Skip to main content
ASoc
Tutorial

How to Learn Next.js by Reading a Real, Shipped Codebase

Courses teach the Next.js API in isolation. This walkthrough traces one real request through five actual files in a shipped storefront, linking to deeper audits at every stop.

The ASoc Team10 min read

The official docs and every course on the "learn Next.js" SERP teach the framework's API against a toy app built to demonstrate one feature at a time. The faster path, once you already know React — and if what you know is the Create React App shape, that gap is the one worth reading first — is reading one real production app end to end: this storefront's own request path from a URL to rendered HTML crosses five real files, none of them longer than 40 lines, and every concept it touches already has a deeper audit linked below.

A map before the tour: where each concept actually lives here

ConceptWhere it lives in this repoRead next
Server Components by defaultEvery file under src/app, src/components/organisms, and src/components/templates that isn't marked "use client"Server Components vs. Client Components
File-based routingsrc/app/**/page.tsx, layout.tsx, route.tsNext.js Routing
Static vs. dynamic per routeThe //ƒ markers in a production build's route tableStatic Rendering in the App Router
Content as typed data, not JSX16 files under src/data/catalog.ts, blog.ts, techStack.tsxAtomic Design in Next.js
Mutations without a separate API layer8 Server Action files under src/lib/actions/checkout.ts, newsletter.ts, redemption.tsNext.js Server Actions
App Router vs. Pages RouterThis repo is 100% App Router — no pages/ directory existsApp Router vs. Pages Router
Per-page metadata, no separate librarygenerateMetadata colocated in each page.tsx — 26 static, 2 dynamic across the siteNext SEO

None of those six rows needs re-deriving here — each already has its own audit. What this post does instead is walk one real request through all six at once, in the order a reader would actually hit them.

Stop 1: the root layout, before any page runs

Every request passes through exactly one file first:

// src/app/layout.tsx (skip link, JSON-LD, and analytics scripts omitted)
const outfit = Outfit({
  variable: "--font-outfit",
  subsets: ["latin"],
  display: "swap",
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className={`${outfit.variable} font-sans antialiased`}>
        {children}
      </body>
    </html>
  );
}

No "use client" here — this is a Server Component, like the vast majority of this codebase, meaning it renders to HTML on the server and ships no JavaScript of its own to the browser. RootLayout never re-runs per navigation; it wraps every route exactly once, which is why the font variable and the <html>/<body> shell live here and nowhere else.

Stop 2: the simplest page in the app

// src/app/page.tsx
export default function Home() {
  return <HomeTemplate />;
}

That's the entire file. A page.tsx in the App Router is a route; this one has no data to fetch, so it renders a template and nothing else. Deeper pages follow the same shape with one addition: data.

Stop 3: a page with data — /templates/[slug]

// src/app/templates/[slug]/page.tsx
export function generateStaticParams(): Params[] {
  return catalog.map((p) => ({ slug: p.slug }));
}

export default async function TemplateDetailPage({
  params,
}: {
  params: Promise<Params>;
}) {
  const { slug } = await params;
  const product = getProduct(slug);
  if (!product) notFound();
  // …JSON-LD structured data omitted here, see the file itself
  return <TemplateDetailTemplate product={product} />;
}

generateStaticParams is the App Router's built-in answer to "which of the 111 products need their own pre-rendered page" — it returns every catalog slug once, at build time, and Next.js generates one static HTML file per entry. There's no loop to write, no manifest to maintain by hand; the function's return value is the list. getProduct(slug) is a plain synchronous lookup into src/data/catalog.ts (no database, no fetch) — one of the 16 data files from the table above, chosen specifically so a product page needs no network round-trip to render.

Stop 4: composing the page from smaller pieces

// src/components/templates/TemplateDetailTemplate.tsx
export default function TemplateDetailTemplate({ product }: { product: TemplateProduct }) {
  return (
    <>
      <Header />
      <main id="main">
        <TemplateDetail product={product} />
        <RelatedProducts product={product} />
      </main>
      <Footer />
    </>
  );
}

This is the "template" layer this codebase's Atomic Design convention names explicitly: it holds no content of its own, only order. product — the one piece of data resolved back in stop 3 — passes straight down to two organisms that each own a <section> of the page. Nothing here decides what renders; it decides where.

Stop 5: the part tutorials skip — metadata, next to the page it describes

// src/app/templates/[slug]/page.tsx
export async function generateMetadata({
  params,
}: {
  params: Promise<Params>;
}): Promise<Metadata> {
  const { slug } = await params;
  const product = getProduct(slug);
  if (!product) return { title: "Template not found" };
  return {
    title: { absolute: productTitle(product) },
    description: productMetaDescription(product),
    alternates: { canonical: `/templates/${slug}` },
  };
}

A from-scratch tutorial usually adds SEO as an afterthought, often via a third-party library like next-seo. This file needs none — generateMetadata is a plain async function, colocated with the page it describes, returning the same shape of object for all 111 products from two small helper functions (productTitle, productMetaDescription in src/lib/productSeo.ts) rather than a library's own component API. Next SEO is the full audit of that native-vs-library decision, including the one thing the native Metadata API deliberately leaves out.

What a production app teaches that a tutorial app can't

A from-scratch tutorial project has one page, so "where does data live" and "what's static vs. dynamic" don't have interesting answers yet. This storefront's own build does, because it has 460 real routes to sort: a fresh production build reports 452 of them static, 8 dynamic — the dashboard, the auth forms, and two API routes, everything else pre-rendered at build time with zero server cost per request. Reading which 8 routes needed the dynamic exception, and why each one does, teaches the static/dynamic decision faster than reading the rule in the abstract — Static Rendering in the App Router is that audit in full.

Troubleshooting

SymptomCauseFix
"Cannot use useState in a Server Component"A component under src/app or an organism was given client-only hooks without "use client"Add the directive at the top of that one file — it doesn't propagate to files that import it
A page renders correctly in dev but the production build fails on itgenerateStaticParams is missing for a dynamic segment that has no dynamic = "force-dynamic" escape eitherAdd generateStaticParams, or explicitly opt the route into dynamic rendering
Data fetched in a Server Component shows up in the client bundleA secret or a large payload was passed as a prop into a Client Component instead of staying server-sideKeep the fetch and any sensitive fields in the Server Component; pass only what the client actually renders
A new content file doesn't show up anywhere on the siteThis repo's data files are explicit arrays, not auto-discovered by a globAdd the entry to the relevant file in src/data/ (or the loader map in src/lib/blog.ts for posts) — nothing scans the filesystem for you
Two components look alike but one won't reuse the other's logicA molecule imported an organism, or an organism imported sideways from another organismFollow the layering rule: compose strictly upward — see Atomic Design in Next.js

FAQ

Do I need to read the whole codebase to learn Next.js this way? No — the four stops above are five files and under 60 lines combined. The six-row map is the reading list for everything past that; each link is a full audit of one concept, so the path is as deep as you want to go.

Is this the same as reading the official Next.js docs? It's a complement, not a replacement. The docs teach the API precisely; reading a shipped app teaches which parts of the API a real product actually reaches for, and which of the framework's many features (this one uses zero loading.tsx, template.tsx, or default.tsx files, per Next.js Routing) a working site can simply skip.

Where should a beginner start if they've never used the App Router? Stop 2 above — a page.tsx with no data at all — then stop 3 once generateStaticParams and a data file make sense together. Server vs. Client Components (stop 1's absence of "use client") is worth understanding before either, since it decides where every other concept is even allowed to run.

What's the fastest way to tell if a route is static or dynamic without reading the code? Run a production build and read its route table: and are pre-rendered, ƒ is server-rendered per request. Static Rendering in the App Router shows the full table for this build and what earned each route its marker.

Templates in this post

ASoc Script, ASoc Seeker, and ASoc Sentinel are three of the 66 landing templates that follow the exact same page.tsx → template → organisms shape as the walkthrough above.

Browse the full set: Next.js landing page templates and Tailwind landing page templates. For the deeper dives this post links out to rather than repeats, start with Next.js Routing and Server Components vs. Client Components.

Keep reading

Tutorial8 min read

We Grepped Our Own Meta Tags. 29% of Titles Ran Long.

187 prerendered pages, checked against the build output rather than the pattern: title and description lengths, what generates them, and where the trade-off is deliberate.

Read more
Tutorial10 min read

next-seo Is Obsolete: 28 Native Metadata Declarations, Zero Package

next-seo is a Pages Router package the native Metadata API replaced in Next 13. Zero dependency here, 28 metadata sources across 27 pages — the full migration mapping.

Read more
Tutorial11 min read

Next.js 16 Renamed Middleware to Proxy: What Changes and What Breaks

Renaming the file is a third of the migration. The exported function has to change too, keeping both files is a build error, and the proxy runs on Node rather than the Edge.

Read more