← Back to thoughts

Dev Thoughts

Building Scalable Web Apps with Next.js

Next.js gives you a lot of power out of the box, but a project only stays fast and maintainable if its structure grows deliberately. These are the patterns I reach for when a side project turns into a product.

Start with the route tree

The App Router's folder structure is your information architecture. Keep routes shallow, colocate components that are only used by one route, and promote anything shared into a top-level components/ folder only once it is used twice.

Fetch on the server by default

Server Components let you read from a database or an API without shipping that code to the browser. Reach for client components only when you need interactivity — forms, animations, anything that touches window.

// app/projects/page.tsx
export default async function ProjectsPage() {
  const projects = await getProjects();
  return <ProjectList projects={projects} />;
}

Cache with intent

Every fetch can be cached, revalidated on a schedule, or forced dynamic. Decide per request rather than per app, and write the decision down in a comment — your future self will thank you.

Ship static where you can

If a page does not depend on the request, prerender it. Static pages are cheaper, faster and impossible to take down with traffic spikes.

Measure before optimising

Use the bundle analyser and Lighthouse before reaching for memoisation or exotic loading strategies. Most slow pages are slow for boring reasons: oversized images, blocking third-party scripts and unbounded data fetching.