← All writing
Engineering4 min read20 July 2026
Server-rendering the portfolio
The homepage fetched its own API from a useEffect, so crawlers saw nothing. Moving to a shared server data layer fixed it in about forty lines.
Most portfolio sites fetch their projects in a useEffect. Mine did too, and it meant a crawler saw an empty page.
The problem with client fetching
The homepage rendered a spinner server-side, then asked /api/projects for the data once React hydrated. Fine for a logged-in dashboard. Bad for a page whose entire job is to be found.
Reading through a shared data layer
export const getProjects = cache(async (): Promise<ProjectRecord[]> => {
const rows = await prisma.project.findMany({
where: { visible: true },
include: FULL_INCLUDE,
orderBy: [{ order: 'asc' }, { createdAt: 'desc' }],
});
return rows.map(shape);
});
Wrapping it in React's cache() means generateMetadata and the component body share one query instead of issuing two.
The API route still exists — the admin panel needs it. It just stopped being how the public site reads.
That's the whole change. The markup ships in the first response now.
- Next.js
- Prisma
- SEO