Wire Next.js to FastAPI
The Hazel Home product grid has been showing the same eight items no matter what the API returns. We are ready to remove that disguise.
The page is a Server Component, so its fetch runs in Node rather than in the browser. Node needs an absolute URL. We'll build one from Vercel's deployment hostname in production and use localhost:3000 during local development.
Outcome
Replace the mockItems array with an async fetch to /api/items that works both under vercel dev locally and in production.
Hands-on exercise 2.2
Build the request URL
In a Client Component, the browser can resolve fetch("/api/items") against the current page. A Server Component runs without that browser context, and Node's fetch requires an absolute URL.
When system environment variables are exposed to a deployment, Vercel provides VERCEL_URL as the deployment hostname, such as hazel-home-abc123.vercel.app. Because the value has no protocol, the production branch adds https://. The local branch uses http://localhost:3000.
Update the page component
Open starter/app/page.tsx and replace the file with this:
type Item = {
id: number;
name: string;
category: string;
price: number;
in_stock: boolean;
};
async function getItems(): Promise<Item[]> {
const base = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "http://localhost:3000";
const res = await fetch(`${base}/api/items`, { 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>
</>
);
}The mockItems array is gone. getItems() builds an absolute URL, checks the response, and returns the decoded inventory. The no-store option keeps this route request-time rendered, so next build does not try to contact a deployment that is still being built. Making Home asynchronous lets the component wait for the data before rendering the existing card markup.
Run vercel dev and refresh
If vercel dev is still running from the last lesson, you should see the updated page after saving. If not, restart it:
cd starter
vercel devOpen http://localhost:3000. The furniture listing appears, this time fetched live from the FastAPI server at http://localhost:3000/api/items.
If you stop vercel dev and reload, Next.js reports a fetch error because the API is unavailable. That failure confirms the page now depends on FastAPI.
Try It
With vercel dev running, confirm the data is coming from FastAPI and not the mock:
- Open
http://localhost:3000. All 8 items appear. - Open
http://localhost:3000/api/itemsin a second tab and compare the response. - Edit one item's name in
api/index.py, save, and hard-refresh the frontend. The name updates.
The edited product name is the useful test: the storefront is reading FastAPI's response rather than its old local array.
Commit
Save the connection between the storefront and API:
git add app/page.tsx
git commit -m "feat(storefront): fetch inventory from FastAPI"Troubleshooting
TypeError: Failed to parse URL in the server logs: This happens if process.env.VERCEL_URL is unset and the fallback didn't kick in. Confirm the ternary is written correctly and that the fallback value starts with http://.
Failed to fetch items from Hazel Home API: vercel dev isn't running, or the FastAPI routes don't match. Check that api/index.py has @app.get("/api/items") (with the /api prefix) and that vercel dev is active on port 3000.
Done-When
page.tsxusesasync function getItems()with nomockItemsarray- The request uses
{ cache: "no-store" }so the page renders at request time - The fetch URL resolves to
http://localhost:3000/api/itemsin local dev http://localhost:3000loads real data from FastAPI throughvercel dev- Editing
api/index.pyand reloading updates what the frontend shows
Solution
// starter/app/page.tsx
type Item = {
id: number;
name: string;
category: string;
price: number;
in_stock: boolean;
};
async function getItems(): Promise<Item[]> {
const base = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "http://localhost:3000";
const res = await fetch(`${base}/api/items`, { 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?