A Webflow Cloud dashboard can render live REST API data through a single server-side Route Handler, giving visitors current metrics while the vendor token stays outside the browser.
A deployed page adds routing constraints that cURL doesn't test. A working deployment keeps the token in server code. It also omits an unsupported runtime directive that can stop the build before the page renders.
Before diving into the setup, it helps to understand the exact stack and prerequisites needed to establish this secure bridge between your REST API and Webflow interface.
What do you need to integrate a REST API with a dashboard UI in Webflow?
You need a Webflow site on Starter or higher, Next.js, npm, REST API credentials, and local Node.js tooling. To mount the deployed app on a custom domain, you need Premium or higher.
Gather these items before you begin:
- Webflow site on Starter or higher: Webflow Cloud is included from the free Starter plan, and a dashboard on a custom domain needs Premium or higher.
- Next.js 15 or higher: Version 15 is the floor Webflow Cloud supports for Next.js, and this build uses Next.js.
- npm as the package manager: Webflow Cloud's docs are explicit: "Currently, Webflow Cloud supports only the npm package manager." Use npm for this build; pnpm and yarn are unsupported.
- A REST endpoint and a token: You need an API you can call from a server with the authentication method its vendor documents, plus a sample response showing which fields the dashboard will display.
- Local Node.js and an editor: A Node.js install with npm covers execution and package management. Use a terminal and editor for the remaining local work.
With these prerequisites ready, you can build the server route, connect the page to it, and deploy both under the same Webflow Cloud mount path.
5 steps to integrate a REST API with a dashboard UI in Webflow Cloud
The build takes five steps: scaffold the Next.js app, configure server and client variables, create the Route Handler, connect the dashboard page, and deploy everything under one matching mount path.
Complete the setup in this order so you can verify each layer before deployment.
1. Scaffold the Next.js app with npm
Scaffold the app with npm so the project produces a package-lock.json file and uses a Next.js version supported by Webflow Cloud.
Create the project in the directory where you keep projects, then open the new project directory before continuing:
npx create-next-app@15 rest-dashboard --typescript --app --no-src-dir --import-alias "@/*" --use-npm
The @15 keeps the major version where Webflow Cloud's middleware support holds, and resolves to the newest 15.x, which is above the 15.2.3 floor named in the troubleshooting section. --no-src-dir puts app/ at the project root, where the file paths in steps 3 and 4 expect it.
cd rest-dashboard
Use the defaults for ESLint and Tailwind if you want them; neither changes anything about the deployment. Keep the project on npm. Leave next.config at its default output configuration: Webflow Cloud's bring-your-own-app page puts it as "No adapter, no base path, no wrangler.json."
You should now have an app/ directory. The project folder should also contain package.json and package-lock.json, and the local development server should serve the default Next.js page on localhost.
2. Store the REST API token as a Webflow Cloud secret
Store the API token in the server-only METRICS_API_TOKEN environment variable, and reserve NEXT_PUBLIC_BASE_PATH for the mount path the client will prepend to its request. Browser JavaScript can read variables with the NEXT_PUBLIC_ prefix.
For local development, create the file Next.js reads automatically:
# .env.local
METRICS_API_TOKEN=your-token-here
NEXT_PUBLIC_BASE_PATH=
Leave NEXT_PUBLIC_BASE_PATH empty locally, because the local development server serves the app at the root. In your Webflow Cloud environment, add METRICS_API_TOKEN as a secret and set NEXT_PUBLIC_BASE_PATH to the path the app will be mounted at, such as /dashboard.
Webflow Cloud's bring-your-own-app page puts it plainly: "Both secret and non-secret environment variables are available to your application's build process ... and remain available to the deployed application at runtime." Secrets are redacted from build logs, so the token does not appear in those logs.
With these variables configured, process.env.METRICS_API_TOKEN resolves inside server code both locally and on the deployed Worker, and process.env.NEXT_PUBLIC_BASE_PATH is available to the client bundle.
3. Write the Route Handler that calls the REST API
Create the Route Handler at app/api/metrics/route.ts and use its default runtime configuration.
Read METRICS_API_TOKEN inside the handler, reject the request if the token is missing, and call the vendor endpoint using the authentication method documented for that API. Check for an unsuccessful upstream response before reading its data, then return only the fields the dashboard needs.
The handler, with the vendor URL as the one value to replace:
// app/api/metrics/route.ts
import { NextResponse } from 'next/server';
// Replace with your vendor's metrics endpoint.
const UPSTREAM_URL = 'https://api.example.com/v1/metrics';
type Metrics = {
signups: number;
revenue: number;
updatedAt: string;
};
export async function GET() {
const token = process.env.METRICS_API_TOKEN;
if (!token) {
return NextResponse.json({ error: 'Metrics API is not configured' }, { status: 500 });
}
let upstream: Response;
try {
upstream = await fetch(UPSTREAM_URL, {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
});
} catch {
return NextResponse.json({ error: 'Metrics API is unreachable' }, { status: 502 });
}
if (!upstream.ok) {
// Report the status only. The upstream body can echo account details.
return NextResponse.json(
{ error: 'Metrics API request failed', upstreamStatus: upstream.status },
{ status: 502 },
);
}
let data: Record<string, unknown>;
try {
data = (await upstream.json()) as Record<string, unknown>;
} catch {
return NextResponse.json({ error: 'Metrics API returned invalid JSON' }, { status: 502 });
}
// Map the vendor's field names to the three the dashboard renders.
const { signups, revenue, updated_at } = data;
if (
typeof signups !== 'number' ||
typeof revenue !== 'number' ||
typeof updated_at !== 'string'
) {
return NextResponse.json({ error: 'Metrics API returned an unexpected shape' }, { status: 502 });
}
const metrics: Metrics = { signups, revenue, updatedAt: updated_at };
return NextResponse.json(metrics);
}
Map the returned fields to whatever the real response calls them; signups, revenue and updated_at above are placeholders for your vendor's names. The type check is deliberate: a vendor that renames a field produces a 502 you can see, not a dashboard quietly showing zero.
I shape the response deliberately so the browser receives only the fields the dashboard renders. Any account identifiers or internal metadata the vendor includes remain on the server. A distinct upstream-failure response identifies the vendor call as the error source.
This sample Route Handler exposes the upstream API without caller authentication or request limits. Before exposing a token-funded vendor API in production, require caller authentication and enforce rate limits.
Also configure a vendor spend or quota cap. An unrestricted route lets anyone who can reach it consume the upstream API through your token. Keep the route private until you have those controls in place.
Run the local development server and open http://localhost:3000/api/metrics. The route should return the fields you selected with live values from your API.
4. Build the dashboard page that fetches through the base path
Build the page at app/page.tsx, so every client-side request prepends the configured Webflow Cloud base path.
Webflow Cloud injects the base path at build time, but client-side requests aren't rewritten, and the bring-your-own-app docs are clear: "Client-side fetch calls must manually include the base path to correctly reach your endpoints." A bare /api/metrics request targets the site root in production and misses the Route Handler.
Mark the page with 'use client' at the top of the file. App Router pages are server components by default, and the loading state and on-load fetch below both need client rendering.
Then read NEXT_PUBLIC_BASE_PATH in the client bundle, prepend its value to /api/metrics, and request the resulting path when the page loads. Render a loading state while the request is pending and an error state when it fails. After the response arrives, render the signups and revenue. Include the updated timestamp with them.
The complete page:
// app/page.tsx
'use client';
import { useEffect, useState } from 'react';
type Metrics = { signups: number; revenue: number; updatedAt: string };
const base = process.env.NEXT_PUBLIC_BASE_PATH ?? '';
export default function Dashboard() {
const [metrics, setMetrics] = useState<Metrics | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(`${base}/api/metrics`)
.then(async (res) => {
if (!res.ok) throw new Error(`Request failed with status ${res.status}`);
return (await res.json()) as Metrics;
})
.then(setMetrics)
.catch((err: unknown) => {
setError(err instanceof Error ? err.message : 'Request failed');
});
}, []);
if (error) return <p role="alert">Could not load metrics: {error}</p>;
if (!metrics) return <p>Loading metrics...</p>;
return (
<main>
<h1>Dashboard</h1>
<dl>
<dt>Signups</dt>
<dd>{metrics.signups.toLocaleString()}</dd>
<dt>Revenue</dt>
{/* Match the currency your vendor reports. */}
<dd>{metrics.revenue.toLocaleString(undefined, { style: 'currency', currency: 'USD' })}</dd>
</dl>
<p>Updated {new Date(metrics.updatedAt).toLocaleString()}</p>
</main>
);
}
Style the metrics however your design system dictates; the request path is what has to survive. If you add more Route Handlers later, give each client request the same base-path prefix.
Locally, http://localhost:3000 should show the two figures and a formatted timestamp after loading.
5. Deploy the app to Webflow Cloud and mount it on the site
Commit package-lock.json, keep .env.local ignored, and use that commit to create a Webflow Cloud project on the site.
Mount the app at the path configured in the environment. A mount at /dashboard with an env var of /dash means the client request prefix no longer matches the mount, so the request never reaches the Route Handler.
Confirm that the site's plan supports its chosen mount before you promise a client the dashboard will live at their brand's own URL.
The request and CPU limits are flat platform values, not plan tiers: 20 seconds of wall time per request, 30 seconds of Worker CPU, 6 simultaneous outgoing requests and 1,000 subrequests. Storage, retention and the number of apps per site are the plan-tiered figures.
Once the build completes, https://your-site-domain/dashboard renders the same two metrics you saw locally, and the page source excludes METRICS_API_TOKEN.
What causes a REST API dashboard in Webflow Cloud to fail?
REST API dashboards usually fail because of an unsupported runtime directive, a missing base-path prefix, an incorrect deployed secret, or middleware behavior that changes after a framework upgrade.
Use the visible symptom to identify the relevant cause and apply its corresponding fix.
The build fails with an unsupported runtime error
Cause: A Route Handler or page includes export const runtime = 'edge', and the OpenNext Cloudflare adapter Webflow Cloud uses to deploy Next.js doesn't support the Next.js edge runtime target.
The directive usually arrives because the bring-your-own-app docs describe the platform as one that "deploys your app using the Edge runtime", and a careful reader takes that as an instruction to opt into the Next.js edge runtime.
The platform and compile target share a name while serving different roles. Webflow Cloud runs on Cloudflare Workers, and OpenNext cannot build the Next.js directive.
Fix: Delete the export const runtime = 'edge' line from every Route Handler and page, then redeploy. Your handler still executes on Workers at the edge, because that is where the whole app runs.
The dashboard loads in production but shows no data
Cause: The client request omitted the base-path value configured for the deployed app. This routing failure can look like a Webflow problem even though the page itself loads successfully.
Webflow Cloud does not rewrite client-side requests, so a bare /api/metrics path points to the site root rather than the mounted application. The request therefore misses the Route Handler while the dashboard remains visible.
Fix: Restore the mount-path value in your Webflow Cloud environment, preserving its leading slash, then redeploy to refresh the client bundle. Open the browser's Network tab on the deployed page and confirm the request URL reads /dashboard/api/metrics, which confirms the prefix is in place.
If it still differs, compare NEXT_PUBLIC_BASE_PATH with the actual mount path character by character. Both values must match before the request can reach the handler.
The REST API returns 401 from the deployed app, but the token works in curl
Cause: The Route Handler is reading an empty or different METRICS_API_TOKEN than the one you tested with. A token stored only in .env.local stays on the local machine because that file is not committed. The mismatch can also come from a variable added to another Webflow Cloud environment.
Also check the pasted secret for a stray quote or trailing space. Because secrets are redacted from build logs, you cannot eyeball the value there, so the mistake stays invisible until the vendor rejects it.
Fix: Temporarily return a diagnostic from the handler, such as token ? token.length : 0, and compare it with the length of your working curl token. Keep the token itself out of the response.
A zero means the variable is missing from that environment; a mismatch means you pasted it incorrectly. Re-enter the secret in the Webflow Cloud environment, redeploy, and remove the diagnostic once the length matches.
Header checks and redirects stop firing after a Next.js upgrade
Cause: The file was renamed from middleware.ts to proxy.ts. The Next.js upgrade notes rename middleware to proxy, and proxy runs on the Node.js runtime with no option to opt into the Edge runtime.
Webflow Cloud's framework-customization docs state the constraint directly: "Node.js runtime middleware isn't supported. Only Edge runtime middleware works on the Workers runtime." So a rename that a Next.js codemod or upgrade guide suggests produces a file that cannot run on Workers. Any header check, redirect, or auth gate for the dashboard must remain in supported middleware.
Fix: Pin the app to Next.js 15 and keep the file named middleware.ts on the Edge runtime. Keeping the filename alone is not enough in 16, where the convention is deprecated, and proxy.ts cannot opt into Edge. Within 15, stay at or above 15.2.3, because earlier 15.x releases carry GHSA-f82v-jwr5-mffw, a critical bypass of a middleware gate.
If you protect the dashboard with a shared-secret header check, one more Workers detail applies. The deployed Workers runtime includes crypto.subtle.timingSafeEqual, a Cloudflare extension to Web Crypto. Local next dev has no such method on crypto.subtle, so a constant-time comparison written against it throws during local development.
Node's own crypto.timingSafeEqual from node:crypto is a different function and is available locally. Guard it with a development fallback, and test the middleware on the deployed app before trusting it in front of a paid API.
What you can build next with REST APIs and Webflow
A second handler can connect the dashboard to a CRM, while a filter control can pass query parameters through to the upstream call. In practice, I usually extend the dashboard without changing the token discipline or the base-path rule.
The Webflow side of the stack is also reachable through the same pattern. Webflow's CMS supports content that editors continue managing in collections. Its expanded Data API can supply that content to an application.
This supports Webflow's native headless direction while third-party metrics remain available on the same page.
Frequently asked questions
File-system compatibility, framework support, build-time secrets, and CMS access shape how this dashboard expands beyond a single vendor endpoint.
Can the Route Handler use a secret during both the build and runtime?
Yes. You can use a server-only secret during both phases because Webflow Cloud environment variables are available to the build process and deployed application. If a statically generated page calls the vendor API during the build, its figures are baked into HTML and remain fixed until your next deployment, which works for summaries rather than live reporting.
Can the Route Handler read a local file with node:fs?
No. You cannot use node:fs because Webflow Cloud pins a Cloudflare Workers compatibility date earlier than the module requires. You can still use node:path when you only need to join or parse paths. For static data, import a JSON module directly so the build bundles it, or retrieve the data from an API.
Which frameworks other than Next.js can I deploy this pattern on?
You can deploy this pattern with Astro or with Vite using React, Vue, Svelte, or vanilla JavaScript, rather than Next.js. You still need to use npm for the Webflow Cloud build and keep server credentials out of browser code. Choose a supported framework version and preserve the mounted app's base path in client-side requests.
Can the same dashboard show Webflow CMS content next to the vendor metrics?
Yes. You can add another Route Handler that retrieves collection items and returns only the fields your page renders. Your editors can keep updating CMS collections while the dashboard combines that content with vendor metrics. Keep API credentials server-side, and let the browser request your handler through the mounted base path used by the metrics endpoint.





