HealthKit is Apple's iOS-only framework with no public REST API — web apps built in Bolt.new cannot query it directly. The practical path is a two-part architecture: a native iOS or React Native app reads HealthKit data and syncs it to a Supabase or Firebase backend, then your Bolt.new web dashboard reads from that backend over HTTP. Alternatively, users can export Apple Health XML and upload it to your Bolt app for parsing.
Build a Health Data Dashboard in Bolt.new Using HealthKit Data
HealthKit sits behind Apple's native SDK — there is no HTTP endpoint you can call from a browser or a Node.js server. Every HealthKit query runs through Swift's `HKHealthStore` class on an iPhone or Apple Watch, protected by iOS's permission model and stored in an encrypted local container. This is fundamentally different from REST APIs like Google Fit or Fitbit, which expose health data over HTTPS and work perfectly in Bolt.new's WebContainer.
The two architectures that work in practice are a backend-sync pattern and an export-parse pattern. In the backend-sync approach, a React Native app (using `react-native-health`) or a native Swift app reads HealthKit data and writes it to a Supabase table or Firebase Firestore collection. Your Bolt.new web app then reads from that same backend via the Supabase or Firebase JavaScript SDK — pure HTTP, no HealthKit involved. The iOS app acts as a bridge, translating HealthKit's local-only data into cloud-accessible records.
The export-parse approach works without a companion app. iOS users can export their entire Health history as a structured XML file from the Health app (Profile → Export All Health Data). This produces an `export.xml` file that users can upload to your Bolt.new app. A Next.js API route parses the XML using the `fast-xml-parser` package and returns structured JSON for your dashboard. This approach is great for one-time imports and health history visualizations, though it does not provide live data. If you need a web-accessible health API with no iOS companion app, consider Google Fit or Fitbit instead — both offer full REST APIs that work in Bolt's WebContainer.
Integration method
Because HealthKit has no REST API, the integration requires a bridge: a native iOS or React Native app writes health data to Supabase or Firebase, and a Bolt.new web app reads that shared backend over HTTP. Alternatively, users export Apple Health XML from the Health app, upload it to your Bolt app, and a server-side API route parses the XML into structured health metrics. Both patterns keep Bolt.new in its comfort zone — consuming HTTP APIs — while respecting Apple's data model.
Prerequisites
- A Bolt.new account with a new Next.js project open
- A Supabase account and project (for the backend-sync pattern) or Apple Health XML export file (for the export-parse pattern)
- Supabase project URL and anon key from your Supabase project Settings → API
- A Netlify account for deployment
- For the backend-sync pattern: a React Native development environment and the react-native-health package installed in your iOS app
Step-by-step guide
Understand the architecture and choose your integration pattern
Understand the architecture and choose your integration pattern
Before writing any code, decide which HealthKit bridge pattern fits your use case. HealthKit is an iOS-only local database with no HTTP interface — Bolt.new's WebContainer cannot call it directly. Two patterns bridge this gap. Pattern A (Backend Sync) is for live or recent data: a React Native app with the `react-native-health` package reads HealthKit and writes daily summaries to a Supabase table. Your Bolt.new dashboard reads that Supabase table via `@supabase/supabase-js`, which communicates over HTTP and works perfectly in WebContainers. This pattern requires an iOS device and React Native development setup outside of Bolt. Pattern B (XML Export) is for historical analysis: users export their Apple Health data from their iPhone (open the Health app, tap your profile photo, tap Export All Health Data, share the ZIP). Inside the ZIP is `export.xml` — a structured XML file with all recorded health samples. Your Bolt.new app accepts this file upload, parses it server-side, and visualizes it. If you need a web-accessible fitness API without any native companion app, consider Google Fit (has a REST API, works in Bolt.new) or Fitbit API instead. The rest of this tutorial covers Pattern B (XML export) for the dashboard and shows the Supabase schema for Pattern A.
Create a new Next.js project with TypeScript and Tailwind CSS. Install these packages: fast-xml-parser, @supabase/supabase-js, recharts, and lucide-react. Create a .env.local file with placeholder values: NEXT_PUBLIC_SUPABASE_URL=your-supabase-url, NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key. Show me the project structure when done.
Paste this in Bolt.new chat
Pro tip: If your primary goal is a web health tracker without iOS development, use Google Fit or Fitbit APIs — both have REST endpoints that work directly in Bolt.new's WebContainer with no companion app required.
Expected result: A new Next.js project with the necessary packages installed and environment variable placeholders in .env.local.
Create the Supabase schema for backend-sync health data (Pattern A)
Create the Supabase schema for backend-sync health data (Pattern A)
If you are building the backend-sync pattern where an iOS app writes HealthKit data to Supabase, you need a well-designed schema. Create this in your Supabase SQL editor. The schema covers the most common HealthKit data types: step count, heart rate, resting heart rate, active energy burned, sleep analysis, and body weight. Each row represents one HealthKit sample — a value at a point in time. The `health_metrics` table stores all sample types in a normalized structure. The `metric_type` column uses the HealthKit type identifier strings (e.g., `HKQuantityTypeIdentifierStepCount`) so that the iOS app can insert records using the same identifier strings from the HealthKit SDK without a mapping layer. Row Level Security ensures each user only sees their own data. Your iOS React Native app (using `react-native-health`) would read HealthKit samples and insert them into this table using the Supabase JavaScript client or REST API. The Bolt.new web dashboard then queries this table with aggregations — daily totals for steps, averages for heart rate, and so on — to power its charts.
1-- Run this in your Supabase SQL editor2CREATE TABLE health_metrics (3 id UUID DEFAULT gen_random_uuid() PRIMARY KEY,4 user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,5 metric_type TEXT NOT NULL,6 value FLOAT8 NOT NULL,7 unit TEXT NOT NULL,8 start_date TIMESTAMPTZ NOT NULL,9 end_date TIMESTAMPTZ NOT NULL,10 source_name TEXT DEFAULT 'Apple Health',11 synced_at TIMESTAMPTZ DEFAULT NOW()12);1314-- Index for efficient date-range queries per user15CREATE INDEX idx_health_metrics_user_date16 ON health_metrics(user_id, metric_type, start_date DESC);1718-- Enable Row Level Security19ALTER TABLE health_metrics ENABLE ROW LEVEL SECURITY;2021CREATE POLICY "Users can read own health data"22 ON health_metrics FOR SELECT23 USING (auth.uid() = user_id);2425CREATE POLICY "Users can insert own health data"26 ON health_metrics FOR INSERT27 WITH CHECK (auth.uid() = user_id);2829-- Daily aggregated view for dashboard queries30CREATE VIEW daily_health_summary AS31SELECT32 user_id,33 metric_type,34 DATE(start_date) AS day,35 SUM(value) AS daily_total,36 AVG(value) AS daily_avg,37 COUNT(*) AS sample_count38FROM health_metrics39GROUP BY user_id, metric_type, DATE(start_date);Pro tip: The React Native companion app should batch-insert HealthKit samples rather than inserting one at a time. Use Supabase's `upsert` with a conflict target on `(user_id, metric_type, start_date)` to avoid duplicates when the app syncs multiple times per day.
Expected result: A health_metrics table with RLS policies and a daily_health_summary view created in your Supabase project.
Build the Apple Health XML parser API route (Pattern B)
Build the Apple Health XML parser API route (Pattern B)
This API route accepts a file upload of the Apple Health XML export, parses it using `fast-xml-parser`, and returns structured JSON with daily aggregates. When users export their health data from iOS, they receive a ZIP file containing `export.xml`. The XML structure has a root `HealthData` element with `Record` child elements, each representing one health sample. Each `Record` has attributes: `type` (HealthKit identifier), `value`, `unit`, `startDate`, `endDate`, and `sourceName`. The parser extracts records for step count, heart rate, resting heart rate, active energy burned, and sleep analysis. It groups step counts by day (summing all samples), averages heart rate readings per day, and counts sleep duration. The response returns a structured object with arrays keyed by metric type, each containing `date` and `value` pairs suitable for Recharts. Note that parsing large XML exports (some users have files over 100MB covering years of data) can be memory-intensive. Add a file size limit and consider streaming parsing for very large files. In Bolt.new's WebContainer, memory constraints are tighter than a deployed server — set a maximum file size of 50MB for development testing and increase it after deployment.
Create a Next.js API route at app/api/health/parse-export/route.ts that accepts a POST request with multipart form data containing a file field named 'healthExport'. Use fast-xml-parser to parse the XML. Extract all Record elements with type HKQuantityTypeIdentifierStepCount, HKQuantityTypeIdentifierHeartRate, HKQuantityTypeIdentifierRestingHeartRate, and HKQuantityTypeIdentifierActiveEnergyBurned. Aggregate step counts as daily totals (sum), heart rate as daily averages. Return a JSON object with keys stepsByDay, heartRateByDay, restingHRByDay, caloriesByDay, each containing arrays of {date: string, value: number}. Add a 50MB file size check and return a 400 error if exceeded.
Paste this in Bolt.new chat
1// app/api/health/parse-export/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { XMLParser } from 'fast-xml-parser';45const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB67interface HealthRecord {8 '@_type': string;9 '@_value': string;10 '@_unit': string;11 '@_startDate': string;12 '@_endDate': string;13}1415interface DailyMetric {16 date: string;17 value: number;18}1920function toDateString(isoDate: string): string {21 return isoDate.split(' ')[0]; // '2024-03-15 08:30:00 +0000' → '2024-03-15'22}2324export async function POST(request: NextRequest) {25 const formData = await request.formData();26 const file = formData.get('healthExport') as File | null;2728 if (!file) {29 return NextResponse.json({ error: 'No file uploaded' }, { status: 400 });30 }31 if (file.size > MAX_FILE_SIZE) {32 return NextResponse.json({ error: 'File exceeds 50MB limit' }, { status: 400 });33 }3435 const text = await file.text();36 const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' });37 const parsed = parser.parse(text);38 const records: HealthRecord[] = parsed?.HealthData?.Record ?? [];3940 const stepsByDay: Record<string, number> = {};41 const heartRateByDay: Record<string, number[]> = {};42 const restingHRByDay: Record<string, number[]> = {};43 const caloriesByDay: Record<string, number> = {};4445 for (const record of records) {46 const day = toDateString(record['@_startDate']);47 const value = parseFloat(record['@_value']);48 if (isNaN(value)) continue;4950 switch (record['@_type']) {51 case 'HKQuantityTypeIdentifierStepCount':52 stepsByDay[day] = (stepsByDay[day] ?? 0) + value;53 break;54 case 'HKQuantityTypeIdentifierHeartRate':55 (heartRateByDay[day] ??= []).push(value);56 break;57 case 'HKQuantityTypeIdentifierRestingHeartRate':58 (restingHRByDay[day] ??= []).push(value);59 break;60 case 'HKQuantityTypeIdentifierActiveEnergyBurned':61 caloriesByDay[day] = (caloriesByDay[day] ?? 0) + value;62 break;63 }64 }6566 const toSortedArray = (map: Record<string, number>): DailyMetric[] =>67 Object.entries(map)68 .map(([date, value]) => ({ date, value: Math.round(value) }))69 .sort((a, b) => a.date.localeCompare(b.date));7071 const avgToArray = (map: Record<string, number[]>): DailyMetric[] =>72 Object.entries(map)73 .map(([date, vals]) => ({ date, value: Math.round(vals.reduce((a, b) => a + b, 0) / vals.length) }))74 .sort((a, b) => a.date.localeCompare(b.date));7576 return NextResponse.json({77 stepsByDay: toSortedArray(stepsByDay),78 heartRateByDay: avgToArray(heartRateByDay),79 restingHRByDay: avgToArray(restingHRByDay),80 caloriesByDay: toSortedArray(caloriesByDay),81 recordCount: records.length,82 });83}Pro tip: Apple Health XML uses a space-separated datetime format ('2024-03-15 08:30:00 +0000') rather than ISO 8601 — use split(' ')[0] to extract the date portion, not new Date() parsing which may fail.
Expected result: A POST request to /api/health/parse-export with a valid Apple Health XML file returns structured JSON with daily step, heart rate, and calorie arrays.
Build the health dashboard UI with file upload and charts
Build the health dashboard UI with file upload and charts
Now build the React dashboard that ties everything together. The UI has two sections: a file upload area where users drag-drop or select their Apple Health export XML, and a charts section that renders after parsing. Use Recharts for the visualizations — it is a pure JavaScript library that installs and runs in Bolt.new's WebContainer without any native modules. The dashboard shows four key metrics as line charts: daily step counts (with a 10,000-step goal reference line), average heart rate trend, resting heart rate trend, and active calories burned. Each chart has a date range selector (last 30 days, 90 days, all time) that filters the parsed data client-side without re-uploading the file. Add a summary cards row above the charts showing: average daily steps, average resting heart rate, and total active calories for the selected period. Use Tailwind CSS for a clean, medical-grade aesthetic — avoid playful colors for health data. The file parsing may take several seconds for large exports. Show a loading spinner with a progress message during parsing. Catch and display errors clearly — common issues include uploading the ZIP instead of extracting it first, or uploading a corrupted XML file.
Build a health dashboard React page at app/page.tsx. It should have: (1) A drag-and-drop file upload zone that accepts .xml files labeled 'Upload your Apple Health export XML'. (2) After upload, call POST /api/health/parse-export with FormData, show a spinner during processing. (3) Once data loads, show summary stat cards for average daily steps, average resting heart rate, and total calories. (4) Show four Recharts LineCharts: daily steps (with a ReferenceLine at 10000), average heart rate, resting heart rate, and active calories burned. (5) Add a date range filter with buttons for Last 30 Days, Last 90 Days, All Time that filters displayed data. Use a clean health-app aesthetic with Tailwind CSS, white cards, and a light blue accent color.
Paste this in Bolt.new chat
1// app/components/HealthChart.tsx2'use client';3import {4 LineChart, Line, XAxis, YAxis, CartesianGrid,5 Tooltip, ResponsiveContainer, ReferenceLine6} from 'recharts';78interface DataPoint { date: string; value: number; }910interface HealthChartProps {11 data: DataPoint[];12 title: string;13 unit: string;14 color?: string;15 goalValue?: number;16 goalLabel?: string;17}1819export function HealthChart({20 data, title, unit, color = '#3b82f6', goalValue, goalLabel21}: HealthChartProps) {22 const formatDate = (dateStr: string) => {23 const d = new Date(dateStr + 'T00:00:00');24 return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });25 };2627 return (28 <div className="bg-white rounded-xl p-5 shadow-sm border border-gray-100">29 <h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-4">30 {title}31 </h3>32 <ResponsiveContainer width="100%" height={200}>33 <LineChart data={data} margin={{ top: 5, right: 10, left: 0, bottom: 5 }}>34 <CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />35 <XAxis36 dataKey="date"37 tickFormatter={formatDate}38 tick={{ fontSize: 11, fill: '#9ca3af' }}39 interval="preserveStartEnd"40 />41 <YAxis tick={{ fontSize: 11, fill: '#9ca3af' }} />42 <Tooltip43 formatter={(value: number) => [`${value.toLocaleString()} ${unit}`, title]}44 labelFormatter={formatDate}45 />46 {goalValue && (47 <ReferenceLine48 y={goalValue}49 stroke="#10b981"50 strokeDasharray="4 4"51 label={{ value: goalLabel ?? `Goal: ${goalValue}`, fontSize: 11, fill: '#10b981' }}52 />53 )}54 <Line55 type="monotone"56 dataKey="value"57 stroke={color}58 strokeWidth={2}59 dot={false}60 activeDot={{ r: 4 }}61 />62 </LineChart>63 </ResponsiveContainer>64 </div>65 );66}Pro tip: Recharts requires client-side rendering — add 'use client' to any component that uses Recharts. Without it, Next.js will try to server-render the charts and throw a hydration error.
Expected result: A working health dashboard that accepts Apple Health XML uploads and renders step count, heart rate, and calorie charts with date filtering.
Deploy to Netlify and configure environment variables
Deploy to Netlify and configure environment variables
Bolt.new's WebContainer is an excellent development environment for this dashboard — outbound API calls work, file uploads parse locally, and the charts render in the preview. However, for the backend-sync pattern (Pattern A with Supabase), you will want to deploy to Netlify or Bolt Cloud so your iOS companion app can reach the production Supabase tables through a stable URL. In Bolt.new, click the Deploy button in the top-right corner and select Netlify, or connect your Bolt project to GitHub and deploy from there. Once deployed, go to your Netlify site dashboard → Site Configuration → Environment Variables and add your Supabase credentials: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY`. For the XML upload pattern, no additional environment variables are needed since parsing happens in-memory without external API calls. After deployment, update your Supabase project settings to add the Netlify domain to the allowed origins (Supabase Dashboard → Authentication → URL Configuration → Add a redirect URL). If you are building the iOS companion app, use your deployed Netlify URL as the API base URL in the React Native app — never the Bolt preview URL, which changes between sessions. During development in Bolt's WebContainer, webhook-based features such as real-time notifications from a companion app cannot receive incoming connections. The WebContainer has no persistent public URL. Only deploy-time Netlify or Bolt Cloud URLs can receive webhooks.
Add a Supabase client configuration to my app. Create a file at lib/supabase.ts that initializes the Supabase client using import.meta.env.VITE_SUPABASE_URL and import.meta.env.VITE_SUPABASE_ANON_KEY for Vite, or process.env.NEXT_PUBLIC_SUPABASE_URL and process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY for Next.js. Create a dashboard page that queries the daily_health_summary view for the logged-in user and displays the last 30 days of steps and heart rate data.
Paste this in Bolt.new chat
1// lib/supabase.ts2import { createClient } from '@supabase/supabase-js';34const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;5const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;67export const supabase = createClient(supabaseUrl, supabaseAnonKey);89// lib/health-queries.ts10import { supabase } from './supabase';1112export async function getHealthSummary(userId: string, days: number = 30) {13 const since = new Date();14 since.setDate(since.getDate() - days);1516 const { data, error } = await supabase17 .from('daily_health_summary')18 .select('metric_type, day, daily_total, daily_avg')19 .eq('user_id', userId)20 .gte('day', since.toISOString().split('T')[0])21 .order('day', { ascending: true });2223 if (error) throw new Error(error.message);2425 const stepsByDay = data26 ?.filter(r => r.metric_type === 'HKQuantityTypeIdentifierStepCount')27 .map(r => ({ date: r.day, value: Math.round(r.daily_total) })) ?? [];2829 const heartRateByDay = data30 ?.filter(r => r.metric_type === 'HKQuantityTypeIdentifierHeartRate')31 .map(r => ({ date: r.day, value: Math.round(r.daily_avg) })) ?? [];3233 return { stepsByDay, heartRateByDay };34}Pro tip: When your iOS companion app first syncs HealthKit data, confirm it is writing to the correct Supabase project by checking the Supabase Table Editor. A mismatch in `SUPABASE_URL` between the iOS app and the web dashboard is the most common reason the dashboard shows no data.
Expected result: Your health dashboard is live on Netlify, reading from Supabase when using the backend-sync pattern, and accessible to iOS companion apps via the stable Netlify domain.
Common use cases
Personal Health History Dashboard
Let users upload their Apple Health XML export and instantly see charts of their step counts, heart rate trends, sleep duration, and active calories over time. A one-time import that transforms years of HealthKit data into an interactive visualization — no iOS companion app required.
Build a health data dashboard in Next.js. Create an API route at /api/health/parse-export that accepts a multipart file upload of Apple Health XML (export.xml), parses it using fast-xml-parser, extracts HKQuantityTypeIdentifierStepCount and HKQuantityTypeIdentifierHeartRate records, and returns JSON arrays of date/value pairs. Show the data in line charts using Recharts with a date range filter. Use Tailwind CSS for styling.
Copy this prompt to try it in Bolt.new
Team Fitness Challenge Board
A web dashboard where a team tracks weekly step counts and active minutes synced from a companion React Native app. Each team member's iOS app writes their daily HealthKit totals to a shared Supabase table, and the Bolt.new leaderboard reads those rows to display rankings, progress bars, and weekly totals.
Build a team fitness leaderboard in Next.js using Supabase. Create a Supabase table called health_metrics with columns: user_id, date, metric_type (text), value (float8), and synced_at (timestamptz). Build an API route at /api/health/leaderboard that queries this week's step counts grouped by user_id, sorted by total steps descending. Display a leaderboard with avatars, names, step counts, and a progress bar toward a 70,000-step weekly goal. Use process.env.NEXT_PUBLIC_SUPABASE_URL and process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY.
Copy this prompt to try it in Bolt.new
Wellness Coaching Client Portal
A coaching business uses this portal to review client health trends. Clients install a React Native app that syncs daily HealthKit summaries (steps, sleep, heart rate variability) to Supabase. The coach logs into the Bolt.new web portal and views individual client dashboards with weekly trend charts and the ability to add coaching notes.
Create a wellness coaching dashboard in Next.js with Supabase authentication. After login, show a list of clients with their average weekly steps and sleep hours from the last 7 days. Create a client detail page that shows 30-day trends for steps, resting heart rate, and sleep duration using Recharts line charts. Add a notes section where coaches can add timestamped coaching notes per client, stored in a Supabase notes table. Protect all routes with Supabase Auth session checks.
Copy this prompt to try it in Bolt.new
Troubleshooting
The XML parser throws 'Cannot read properties of undefined (reading map)' when processing the export file
Cause: The Apple Health export is a ZIP file — users upload the ZIP directly instead of first extracting the XML file from inside it. The ZIP contains a folder called 'apple_health_export' with export.xml inside.
Solution: Update the error message in your upload zone to say 'Upload the export.xml file from inside the Apple Health ZIP — not the ZIP itself'. Optionally, add a file type check in the API route that rejects files not ending in .xml and returns a clear error message.
1// Add to the parse-export route before parsing2if (!file.name.endsWith('.xml')) {3 return NextResponse.json(4 { error: 'Please upload the export.xml file, not the ZIP archive. Extract the ZIP first, then upload the export.xml file inside the apple_health_export folder.' },5 { status: 400 }6 );7}Dashboard shows no data after parsing — the API returns empty arrays for all metrics
Cause: The export.xml file is from a device with very little health data recorded, or the fast-xml-parser is not handling the HealthKit date format correctly. The Record elements may also be nested differently in different iOS versions.
Solution: Add a diagnostic endpoint that returns the raw count of parsed records and the first 5 records to inspect the structure. Also check that you are using ignoreAttributes: false in the XMLParser options — without this flag, the @_type attribute is not available.
1// Temporary debug route at app/api/health/debug/route.ts2export async function POST(request: NextRequest) {3 const formData = await request.formData();4 const file = formData.get('healthExport') as File;5 const text = await file.text();6 const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' });7 const parsed = parser.parse(text);8 const records = parsed?.HealthData?.Record ?? [];9 return NextResponse.json({10 totalRecords: records.length,11 sampleTypes: [...new Set(records.slice(0, 100).map((r: any) => r['@_type']))],12 firstRecord: records[0] ?? null,13 });14}Supabase dashboard shows no health_metrics rows even though the iOS companion app claims to have synced
Cause: The iOS app is using a different Supabase project URL or anon key than the web dashboard. Another common cause is that Row Level Security is blocking the insert because the iOS app is not authenticated as the correct user.
Solution: In the iOS app, log the Supabase response from the insert call — look for a 401 or 403 status. Ensure the iOS app calls supabase.auth.signIn() before inserting health records, so the JWT in the request matches the user_id in the health_metrics row. Double-check that SUPABASE_URL in the iOS app matches the project URL shown in your Bolt .env file.
The WebContainer preview shows a blank chart area with no error after uploading and parsing a valid XML file
Cause: Recharts components are not wrapped in 'use client' directive, causing a server-side rendering failure in Next.js App Router. The chart library depends on browser APIs (window dimensions) that are unavailable during SSR.
Solution: Add 'use client' as the first line of any file that imports Recharts components. If the chart is in a server component, extract it into a separate client component file.
1// Add to the top of any file using Recharts2'use client';3import { LineChart, Line, ... } from 'recharts';Best practices
- Always explain to users that they must extract the ZIP and upload the inner export.xml file — this is the single most common point of confusion in Apple Health integrations
- Enforce a file size limit (50MB for development, up to 200MB for production) and show progress feedback for large XML files that may take 5-15 seconds to parse
- Never store parsed health data in browser localStorage or sessionStorage — health data is sensitive and should only persist in your secured Supabase backend
- Use the daily_health_summary view rather than querying raw health_metrics rows — aggregating millions of raw samples on the client side is slow and leaks privacy
- If building a backend-sync iOS companion app, implement incremental sync (only new samples since the last sync timestamp) rather than uploading all historical data on every sync
- For the Supabase pattern, always enable Row Level Security on health_metrics and never use the service role key on the iOS client — the anon key with an authenticated user is sufficient
- Consider Google Fit or Fitbit API as first-class alternatives for projects that don't require HealthKit specifically — both have full REST APIs that work without a native iOS companion app
- Test with real exported health data before launching — synthetic test data often misses edge cases in the HealthKit XML format like multi-source aggregations and watch vs. iPhone discrepancies
Alternatives
Google Fit has a full REST API accessible from web apps — no native iOS companion needed, making it far simpler to integrate directly in Bolt.new without a bridge architecture.
Fitbit exposes step counts, heart rate, sleep, and activity data via a REST API with OAuth 2.0, making it a direct web-accessible alternative to HealthKit for fitness dashboards.
Garmin Connect IQ provides health and fitness data from Garmin wearables via a REST API, suitable for athletes who prefer Garmin devices over Apple Watch.
Withings offers a REST API for body weight, blood pressure, heart rate, and sleep data from their connected health devices — ideal for clinical or wellness-focused dashboards.
Frequently asked questions
Can I directly access HealthKit from a Bolt.new web app?
No. HealthKit is an iOS and watchOS framework that runs exclusively on Apple devices through Apple's native SDK. There is no public REST API or HTTP endpoint. Web apps — including those built in Bolt.new — cannot query HealthKit directly. You need a bridge: either a native iOS app that syncs data to a web-accessible backend like Supabase, or the Apple Health XML export approach.
Does the Apple Health export approach give me real-time data?
No. The export approach is a one-time snapshot of historical data up to the moment of export. For near-real-time health data in a web app, you need the backend-sync pattern with a React Native iOS companion app using react-native-health that periodically writes new HealthKit samples to your Supabase database.
What is the difference between HealthKit and Google Fit for a Bolt.new integration?
The difference is significant for web development. HealthKit is iOS-only with no web API — you cannot call it from Bolt.new without a native bridge. Google Fit has a full REST API (the Fitness API) protected by OAuth 2.0 that any web app can call over HTTP. For a web-first project, Google Fit is dramatically simpler to integrate in Bolt.new.
Can I read iPhone step counts without building a native iOS app?
Yes, via the Apple Health XML export. Users can export all their health data from the iPhone Health app (profile photo → Export All Health Data), extract the ZIP, and upload the export.xml file to your Bolt.new app. Your API route parses the XML and extracts step count records. This approach works without any native app development but requires manual user action each time they want to update the data.
Will the XML parser work in Bolt.new's WebContainer during development?
Yes. The fast-xml-parser package is pure JavaScript with no native dependencies, so it installs and runs in WebContainers without any issues. Parsing moderately sized XML files (under 50MB) works well in the Bolt preview. Very large exports (100MB+) may hit browser memory limits in the WebContainer — test with a smaller date range export first.
Can my Bolt app receive live health updates from an iOS app in real time?
Not during Bolt's WebContainer development phase. WebContainers cannot receive incoming connections, so webhooks or push notifications from an iOS app cannot reach the preview URL. After deploying to Netlify or Bolt Cloud, your app has a stable URL that can receive webhooks. For real-time data, use Supabase Realtime subscriptions — the iOS app writes to Supabase, and the Bolt dashboard subscribes to row changes using WebSockets.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation