Wire Next.js to FastAPI
The backend can change its inventory, but the storefront still shows the same furniture. Let's replace that local array with a request to FastAPI.
Outcome
Render the storefront from FastAPI data using the frontend's generated service binding.
Hands-on exercise 2.2
Read the binding
Open starter/frontend/app/page.tsx. Add the connection import at the top, keep the Item type, remove the mockItems array, and add this function above Home:
import { connection } from "next/server";
async function getItems(): Promise<Item[]> {
await connection();
const backendUrl = process.env.BACKEND_URL;
if (!backendUrl) {
throw new Error("BACKEND_URL is missing. Run vercel dev from the folder containing vercel.json.");
}
const res = await fetch(new URL("api/items", backendUrl), { cache: "no-store" });
if (!res.ok) throw new Error("Failed to fetch items from Hazel Home API");
return res.json();
}BACKEND_URL comes from the frontend binding in vercel.json. It is already an absolute URL base. new URL resolves the API path against it, and the fetch runs on the server. The address stays out of browser code, so it needs no NEXT_PUBLIC_ prefix.
Bindings are available at runtime. Calling await connection() waits for an incoming request before reading BACKEND_URL. This keeps the missing-binding check out of build-time prerendering. The no-store option keeps inventory responses out of the fetch cache. Checking res.ok catches a failed backend response before the page tries to render it as inventory.
Connect the page
Make Home asynchronous, call getItems(), and change the product loop from mockItems.map to items.map:
export default async function Home() {
const items = await getItems();
// Keep the existing card markup and render items.map(...) here.
}The full page is in the Solution below. The visible storefront stays the same; its inventory now comes from Python.
Run through Services
Keep vercel dev -L running from starter/, or restart it there:
vercel dev -LOpen the local storefront URL. Running npm run dev inside frontend/ alone does not create the binding, so the connected page requires the Services development server.
The binding resolves to the backend in the same deployment: a preview frontend calls its preview backend, and production calls its production backend. We don't need to construct a public deployment hostname or set BACKEND_URL manually.
Try It
- Open the storefront and confirm that eight items appear.
- Open
/api/itemson the same local origin and compare the data. - Change
Fernwood SectionaltoFernwood Sectional, back in stockinbackend/main.py. - Wait for the backend to reload, then refresh the storefront. The new name should appear, confirming that the page reads the backend response.
- Restore the original name before committing.
Commit
From starter/, save the connected page:
git add frontend/app/page.tsx
git commit -m "feat(storefront): fetch inventory through backend binding"Troubleshooting
BACKEND_URL is missing: Start vercel dev -L from starter/, and confirm the binding is declared under the frontend service with env set to BACKEND_URL. Restart the CLI after configuration changes.
The build reports a missing binding: Keep await connection() before reading BACKEND_URL. The binding is only injected at runtime, and the later no-store fetch cannot prevent an earlier missing-binding error.
The backend returns 404: The internal request also needs the application's /api/items route. Check the URL passed to fetch and the route in backend/main.py.
The storefront still shows old data: Remove mockItems, render the fetched items, and keep { cache: "no-store" }. Check the backend terminal for a reload error.
Done-When
frontend/app/page.tsxhas nomockItemsarraygetItems()awaitsconnection()before reading and checkingBACKEND_URL- The fetch resolves
api/itemsagainst the binding URL and usesno-store Homeis async and renders the returned inventory- A backend item edit appears in the storefront after refresh
Solution
Replace starter/frontend/app/page.tsx with this Server Component. It fetches inventory through the service binding on each request:
import { connection } from "next/server";
type Item = {
id: number;
name: string;
category: string;
price: number;
in_stock: boolean;
};
async function getItems(): Promise<Item[]> {
await connection();
const backendUrl = process.env.BACKEND_URL;
if (!backendUrl) {
throw new Error("BACKEND_URL is missing. Run vercel dev from the folder containing vercel.json.");
}
const res = await fetch(new URL("api/items", backendUrl), { cache: "no-store" });
if (!res.ok) throw new Error("Failed to fetch items from Hazel Home API");
return res.json();
}
export default async function Home() {
const items = await getItems();
return (
<>
<h2 className="text-2xl font-semibold mb-8">All Furniture</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{items.map((item) => (
<div
key={item.id}
className="bg-white border border-stone-200 rounded-lg p-6"
>
<p className="text-xs uppercase tracking-widest text-stone-400 mb-1">
{item.category}
</p>
<h3 className="text-lg font-medium mb-3">{item.name}</h3>
<div className="flex items-center justify-between">
<span className="text-lg font-semibold">
${item.price.toLocaleString()}
</span>
<span
className={`text-xs px-2 py-1 rounded-full ${
item.in_stock
? "bg-emerald-50 text-emerald-700"
: "bg-stone-100 text-stone-400"
}`}
>
{item.in_stock ? "In stock" : "Out of stock"}
</span>
</div>
</div>
))}
</div>
</>
);
}Was this helpful?