nextjsapitutorialtypescript

Integrating 130 Body Dimensions into Your Next.js App (15-Minute Guide)

· 4 min read · Martin Hejda

Body measurement features tend to follow the same pattern in most apps: you need them for sizing, onboarding, or personalization, but you don’t want to build a model, store biometric data, or deal with photo upload pipelines. A REST API that accepts height and weight and returns structured dimensional data solves all three.

This guide walks through a complete Next.js integration — server-side API route for key security, React component for the UI, and response parsing for the data you actually need.


Why server-side matters here

Never call a body measurement API directly from the browser. Your RapidAPI key would be exposed in network requests. In Next.js, route handlers (or pages/api/ routes) run on the server — your key stays private, and you control what data reaches the client.


Step 1: The API route

Create app/api/body-dimensions/route.ts:

import { NextRequest, NextResponse } from 'next/server';

const RAPIDAPI_KEY = process.env.RAPIDAPI_KEY!;
const RAPIDAPI_HOST = 'dimensionspot-bodysize-engine.p.rapidapi.com';

export async function POST(req: NextRequest) {
  const { gender, height_cm, weight_kg, region } = await req.json();

  // Validate inputs before sending to the API
  if (!gender || !height_cm || !weight_kg) {
    return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
  }

  const payload = {
    input_data: {
      input_unit_system: 'metric',
      subject: {
        gender: gender.toLowerCase(),
        input_origin_region: region || 'GLOBAL',
      },
      anchors: {
        body_height: height_cm * 10, // API expects mm, not cm
        body_mass: weight_kg,
      },
    },
    output_settings: {
      calculation: {
        calculation_model: 'AUTO',
        target_region: region || 'GLOBAL',
        body_build_type: 'CIVILIAN',
      },
      requested_dimensions: { bundle: 'TORSO' },
      output_format: {
        unit_system: 'metric',
        include_range_95: true,
        include_iso_codes: false,
      },
    },
  };

  const response = await fetch(
    `https://${RAPIDAPI_HOST}/v1/predict`,
    {
      method: 'POST',
      headers: {
        'X-RapidAPI-Key': RAPIDAPI_KEY,
        'X-RapidAPI-Host': RAPIDAPI_HOST,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(payload),
    }
  );

  if (!response.ok) {
    const error = await response.json();
    return NextResponse.json({ error }, { status: response.status });
  }

  const data = await response.json();
  return NextResponse.json(data);
}

Unit note: The API expects body_height in millimeters. A user entering 175cm needs height_cm * 10 = 1750. This is the most common integration mistake — the validator will return a 422 if you send centimeters.


Step 2: The React component

'use client';

import { useState } from 'react';

interface Dimension {
  value: number;
  unit: string;
  confidence_score: number;
  range_95: [number, number] | null;
  type: string;
}

interface BodyData {
  body_dimensions: Record<string, Dimension>;
}

export function BodyProfileForm() {
  const [gender, setGender] = useState('female');
  const [height, setHeight] = useState('');
  const [weight, setWeight] = useState('');
  const [result, setResult] = useState<BodyData | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setLoading(true);
    setError('');

    const res = await fetch('/api/body-dimensions', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        gender,
        height_cm: Number(height),
        weight_kg: Number(weight),
        region: 'GLOBAL',
      }),
    });

    const data = await res.json();
    if (!res.ok) {
      setError(data.error?.detail || 'Prediction failed');
    } else {
      setResult(data);
    }
    setLoading(false);
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <select value={gender} onChange={e => setGender(e.target.value)}>
          <option value="female">Female</option>
          <option value="male">Male</option>
        </select>
        <input
          type="number"
          placeholder="Height (cm)"
          value={height}
          onChange={e => setHeight(e.target.value)}
          required
        />
        <input
          type="number"
          placeholder="Weight (kg)"
          value={weight}
          onChange={e => setWeight(e.target.value)}
          required
        />
        <button type="submit" disabled={loading}>
          {loading ? 'Predicting…' : 'Get dimensions'}
        </button>
      </form>

      {error && <p style={{ color: 'red' }}>{error}</p>}

      {result && <DimensionTable data={result.body_dimensions} />}
    </div>
  );
}

function DimensionTable({ data }: { data: Record<string, Dimension> }) {
  const KEY_DIMS = ['chest_circumference', 'waist_circumference_natural', 'hip_circumference'];
  
  return (
    <table>
      <thead>
        <tr>
          <th>Dimension</th>
          <th>Value</th>
          <th>Type</th>
          <th>Confidence</th>
          <th>95% interval</th>
        </tr>
      </thead>
      <tbody>
        {KEY_DIMS.filter(k => data[k]).map(key => {
          const d = data[key];
          const cm = (d.value / 10).toFixed(1);
          const range = d.range_95
            ? `${(d.range_95[0] / 10).toFixed(1)} – ${(d.range_95[1] / 10).toFixed(1)} cm`
            : '—';
          return (
            <tr key={key}>
              <td>{key.replace(/_/g, ' ')}</td>
              <td>{cm} cm</td>
              <td>{d.type}</td>
              <td>{d.confidence_score}</td>
              <td>{range}</td>
            </tr>
          );
        })}
      </tbody>
    </table>
  );
}

Step 3: Understanding the response shape

A successful response looks like this:

{
  "header": {
    "gender": "female",
    "anchor_tier": "PRIMARY_BOTH",
    "target_region": "GLOBAL"
  },
  "body_dimensions": {
    "chest_circumference": {
      "value": 893.2,
      "unit": "mm",
      "type": "FLESH",
      "confidence_score": 78,
      "range_95": [841.5, 945.0]
    },
    "waist_circumference_natural": {
      "value": 714.8,
      "unit": "mm",
      "type": "FLESH",
      "confidence_score": 77,
      "range_95": [671.0, 758.6]
    }
  },
  "system_info": {
    "api_version": "1.4.0",
    "model_version": "adult_ridge_v4.0",
    "computation_time_ms": 4
  }
}

Values are in millimeters. Divide by 10 for centimeters. confidence_score runs 0–100 — for height + weight input, FLESH dimensions (circumferences) cluster around 76–80, BONE dimensions (skeletal lengths) around 83–87.


Step 4: Adding accuracy with one extra input

If users know their waist size (many do, from clothing), add it as an anchor:

anchors: {
  body_height: height_cm * 10,
  body_mass: weight_kg,
  waist_circumference_omphalion: waist_cm * 10, // optional
}

This upgrades the request to PRIMARY_RICH tier. FLESH confidence rises by roughly 2–3 points. For a sizing feature, that’s a meaningful improvement at zero UX cost — users entering their waist from existing clothes is a natural onboarding question.


Environment setup

Add to .env.local:

RAPIDAPI_KEY=your_key_here

The free tier on RapidAPI provides 100 requests/month — enough to develop and test the full integration without a credit card.


What to do with the data downstream

The API is stateless — nothing is stored on the provider side. What you store is your decision. For a sizing feature, you typically want to persist only the derived output (e.g., “recommended size: M”), not the raw numerical measurements. This keeps your own data store free of biometric data, which simplifies your GDPR obligations considerably.

For a fitness app building a body profile over time, you’d store the dimensions with a timestamp and user ID — but even then, you own the storage decision entirely.


This integration pattern works identically in standard Next.js API routes (pages/api/) if you’re not using the App Router. The core payload and response structure are the same regardless of framework.

Try DimensionsPot

Free tier — 100 requests/month, no credit card required.

Get API on RapidAPI