Next.js & React
Structuring a Next.js App Router Project That Stays Maintainable
Route groups, colocated components and a thin data layer: the folder structure I reach for when a Next.js project needs to grow without turning into a maze.
Most Next.js projects start tidy. The trouble begins around the tenth page, when a second layout appears, an admin area gets bolted on and data fetching ends up scattered across components. A little structure early on saves a lot of refactoring later.
Start with route groups
Route groups — folders wrapped in parentheses — organise routes without changing the URL. They are the cleanest way to give different areas of a site different layouts.
app/
(public)/
layout.tsx // navbar + footer
page.tsx // "/"
projects/
page.tsx // "/projects"
[slug]/page.tsx
admin/
layout.tsx // sidebar shell
page.tsx // "/admin"The public pages share a marketing layout, the admin area gets its own shell, and neither leaks into the other.
Keep data access in one place
Components should not know where data comes from. I put every read behind small async functions in a lib/data folder:
// lib/data/projects.ts
import { projects } from "@/data/projects";
export async function getProjectBySlug(slug: string) {
return projects.find((p) => p.slug === slug && p.status === "published") ?? null;
}Today those functions read local TypeScript files. Tomorrow they can call a database or a REST API, and not a single page component has to change.
Server components by default
Pages and layouts are server components unless they need interactivity. I only add "use client" to the leaves that need state or browser APIs: a filter bar, a form, a menu toggle. That keeps JavaScript bundles small and lets data load on the server.
- Server: pages, layouts, lists, article bodies
- Client: forms, search inputs, carousels, anything with
useState
Share types between UI and data
A single types/content.ts file describing a Project or a BlogPost means the admin form, the listing card and the detail page all agree on the shape of the data. When a field changes, TypeScript points at every place that needs updating.
Metadata belongs next to the page
Each route exports its own generateMetadata, built from the same data the page renders. Titles and descriptions never drift away from the content.
A good structure is one where a new teammate can guess where a file lives before searching for it.
None of this is exotic. It is simply a set of boundaries — routes, data, types and client islands — that keep a growing Next.js codebase predictable.