Chapters 1 to 5 gave you the concepts: the HTTP loop, the DOM tree, the CSS cascade, the token system, the JavaScript runtime. This chapter closes Part 1 by dissecting an actual production codebase — the one you're reading this chapter on.
Everything you see — the article layout, the fence of mermaid diagrams, the dark-mode theme, the article pager, the sitemap — is real code sitting in a real Git repo, deployed to a real edge network. We're going to open it and walk every pattern you just learned, in context.
By the end you'll understand the stack well enough to fork your own version on day one, and you'll have a mental template for how the Chs 1-5 concepts compose into a real product.
The Stack, In One Glance
You already met this stack in Chapter 1's Figure 4. Here it is applied to this site specifically:
Figure 1 — The request lifecycle for this site. Everything runs on Cloudflare: static assets are served from the edge cache, the Worker fills cache misses, MDX files are bundled into the Worker at build time.
Five production pieces:
- Next.js 16 + React 19 — the framework that renders every page.
- OpenNext Cloudflare adapter (
@opennextjs/cloudflare) — compiles the Next.js app into a single Cloudflare Worker + a bundle of static assets. - MDX via
next-mdx-remote/rsc+gray-matter— tutorial content in plain.mdxfiles with YAML frontmatter. - Mermaid — client-side-rendered diagrams embedded in MDX (the thing you're looking at right now).
- Cloudflare Workers + custom domain — the runtime. One Worker serves simpleappshipper.com + www.
No separate CDN, no separate server, no origin in us-east-1. The whole site is one Worker, running in the edge location closest to whoever's reading.
The File Tree
Here's the real directory structure of website-next/, trimmed to the parts we'll touch:
website-next/
├── package.json # scripts: dev, build, cf:build, cf:deploy
├── next.config.ts # Next.js config
├── open-next.config.ts # OpenNext Cloudflare config
├── wrangler.toml # Worker routes + bindings
├── tsconfig.json
├── public/ # static files copied verbatim
└── src/
├── app/ # Next.js App Router
│ ├── layout.tsx # root HTML shell
│ ├── page.tsx # homepage
│ ├── globals.css # design tokens + base styles
│ ├── sitemap.ts # sitemap.xml generator
│ ├── tutorials/
│ │ ├── page.tsx # tutorial hub listing
│ │ └── web/
│ │ └── [slug]/page.tsx # dynamic route for Web chapters
│ └── api/ # edge API routes
├── content/ # MDX source
│ └── web/
│ └── *.mdx # each Web-series chapter
├── components/
│ ├── ArticleLayout.tsx # chapter page layout + pager + CTA
│ ├── MDXContent.tsx # MDX renderer + mermaid interception
│ ├── MermaidDiagram.tsx # client-side mermaid renderer
│ ├── Callout.tsx # <Callout> variant boxes
│ ├── Navigation.tsx # top nav
│ └── Footer.tsx
└── lib/
└── articles.ts # MDX loader (uses gray-matter)
Twelve paths. That's it. Every chapter you've read is one .mdx file under src/content/web/. The dynamic route (src/app/tutorials/web/[slug]/page.tsx) looks up the MDX by slug, feeds it through MDXContent, wraps it in ArticleLayout, and ships the rendered HTML.
The Request Lifecycle of This Very Page
Trace what happened when you clicked the link to this chapter:
Figure 2 — The full request lifecycle, from click to hydrated diagrams. The first visit from any region pays for the Worker execution; everyone after rides the edge cache.
Why the HTML is static-ish: the chapter's [slug]/page.tsx exports generateStaticParams() — at build time, Next enumerates every .mdx file and prerenders a static HTML page for each. The Worker's job on most requests is just to serve that prerendered HTML with long cache headers. A true cache hit skips the Worker entirely — Cloudflare returns the cached HTML directly from the edge node nearest the user.
Loading MDX — articles.ts Is Just 60 Lines
Here's the real code that loads every chapter. It lives in src/lib/articles.ts:
import fs from "fs";
import path from "path";
import matter from "gray-matter";
export interface ArticleMeta {
title: string;
description: string;
chapter: number;
series: string;
level: string;
readTime: string;
publishedDate: string;
prevSlug: string | null;
nextSlug: string | null;
prevTitle?: string;
nextTitle?: string;
}
export interface Article {
slug: string;
meta: ArticleMeta;
content: string;
}
function getContentDir(series: string): string {
return path.join(process.cwd(), "src", "content", series);
}
export function getArticle(series: string, slug: string): Article | null {
const filePath = path.join(getContentDir(series), `${slug}.mdx`);
if (!fs.existsSync(filePath)) return null;
const raw = fs.readFileSync(filePath, "utf-8");
const { data, content } = matter(raw);
return {
slug,
meta: data as ArticleMeta,
content,
};
}
export function getAllArticleSlugs(series: string): string[] {
const dir = getContentDir(series);
if (!fs.existsSync(dir)) return [];
return fs
.readdirSync(dir)
.filter((f) => f.endsWith(".mdx"))
.map((f) => f.replace(".mdx", ""));
}
Read that. It's nothing. gray-matter splits the frontmatter (the --- title: ... block at the top) from the Markdown body. That's the whole "content system." There's no CMS, no database, no API. Chapters are files; fs.readFileSync loads them at build time; Next turns them into static pages.
The [slug] Dynamic Route
This is what turns a URL like /tutorials/web/dissecting-simpleappshipper into a rendered page. The whole file:
// src/app/tutorials/web/[slug]/page.tsx
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { getArticle, getAllArticleSlugs } from "@/lib/articles";
import ArticleLayout from "@/components/ArticleLayout";
import MDXContent from "@/components/MDXContent";
interface Props {
params: Promise<{ slug: string }>;
}
export async function generateStaticParams() {
const slugs = getAllArticleSlugs("web");
return slugs.map((slug) => ({ slug }));
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const article = getArticle("web", slug);
if (!article) return { title: "Not Found" };
return {
title: `${article.meta.title} - Ultimate Web Development Series`,
description: article.meta.description,
openGraph: { title: article.meta.title, description: article.meta.description, type: "article" },
};
}
export default async function WebArticlePage({ params }: Props) {
const { slug } = await params;
const article = getArticle("web", slug);
if (!article) notFound();
return (
<ArticleLayout
meta={article.meta}
basePath="/tutorials/web"
seriesName="Ultimate Web Development Series"
>
<MDXContent source={article.content} />
</ArticleLayout>
);
}
Three responsibilities in 40 lines:
generateStaticParams— at build time, list every slug so Next prerenders all the pages. This is how Ch 6 got an HTML file before you even visited.generateMetadata— feed the frontmatter into Next's<title>/ meta tags / OpenGraph. This is why sharing a chapter on social media previews the right title.WebArticlePage— the component. Load the article, 404 if the slug doesn't match a file, otherwise render the layout + MDX.
The pattern repeats identically for /tutorials/swift/[slug]/page.tsx and /tutorials/business/[slug]/page.tsx. Three series, one abstraction — change the "web" string to add a new one.
Rendering MDX — The Mermaid Interception
MDXContent.tsx is where the Markdown becomes JSX. This one file is why fenced ```mermaid blocks render as SVG diagrams instead of plain code:
// src/components/MDXContent.tsx
import { MDXRemote } from "next-mdx-remote/rsc";
import type { ReactNode, ReactElement } from "react";
import { isValidElement } from "react";
import MermaidDiagram from "./MermaidDiagram";
import Callout from "./Callout";
interface CodeProps { className?: string; children?: ReactNode }
function PreBlock({ children }: { children?: ReactNode }) {
const child = Array.isArray(children) ? children[0] : children;
if (isValidElement(child)) {
const { className, children: code } = (child as ReactElement<CodeProps>).props;
if (className === "language-mermaid") {
return <MermaidDiagram chart={typeof code === "string" ? code : String(code ?? "")} />;
}
}
return <pre>{children}</pre>;
}
const components = {
Mermaid: MermaidDiagram,
Callout,
pre: PreBlock,
};
export default function MDXContent({ source }: { source: string }) {
return <MDXRemote source={source} components={components} />;
}
Read the PreBlock carefully. When MDX compiles a fenced code block, the output shape is:
<pre>
<code className="language-mermaid">
graph TD ...
</code>
</pre>
We intercept at the <pre> level. If the child code element has className="language-mermaid", we swap in <MermaidDiagram> and drop the <pre>. Everything else (ts, css, ```html) passes through to a normal <pre> and gets default code-block styling.
This is the MDX component-override pattern in a nutshell: extend the default HTML tag behavior by passing a replacement via components. Works for any tag — h2, p, a, img, pre, code. A whole design language for your content, added in 15 lines.
The Mermaid Client Component
MermaidDiagram.tsx is the file that makes every diagram in this series actually draw on screen. It's a client component (the "use client" directive forces it to hydrate in the browser — mermaid needs DOM APIs it can't access on the server).
The essential shape:
"use client";
import { useEffect, useState } from "react";
export default function MermaidDiagram({ chart }: { chart?: string }) {
const [svg, setSvg] = useState<string>("");
const [error, setError] = useState<string>("");
useEffect(() => {
if (!chart) return;
let cancelled = false;
(async () => {
const mermaid = (await import("mermaid")).default;
mermaid.initialize({
theme: "base",
flowchart: { htmlLabels: false, useMaxWidth: true },
themeVariables: { /* site-matched color palette */ },
});
try {
const { svg } = await mermaid.render("m-" + Math.random(), chart.trim());
if (!cancelled) setSvg(svg);
} catch (err) {
if (!cancelled) setError((err as Error).message);
}
})();
return () => { cancelled = true; };
}, [chart]);
if (error) return <div role="alert">Diagram error: {error}</div>;
return <div dangerouslySetInnerHTML={{ __html: svg }} />;
}
Four things to notice:
- Dynamic
import("mermaid")— the library is ~2MB. Loading it dynamically means the 500KB of React hydration ships first; mermaid only arrives for pages that actually have diagrams. htmlLabels: false— SVG-native text so labels never clip (the fix we made in Ch 1's live-bug callout).cancelledflag — prevents a state update if the user navigates away before mermaid finishes. Classic React async pattern.dangerouslySetInnerHTML— mermaid outputs an SVG string; injecting raw SVG is the simplest way to get it on screen. Safe here becausechartcomes from build-time MDX, not user input.
The Dark-Mode Token System
Every color in this page is a CSS custom property from src/app/globals.css. The design system from Chapter 4 applied for real:
:root {
--color-bg: #ffffff;
--color-surface: #f5f5f7;
--color-text: #1d1d1f;
--color-muted: #6e6e73;
--color-border: #e5e5e7;
--color-accent: #0071e3;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.05);
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0a0a0a;
--color-surface: #1a1a1a;
--color-text: #e5e5e7;
--color-muted: #86868b;
--color-border: #2a2a2a;
}
}
Every component reads from those variables. When your browser's prefers-color-scheme flips, all colors update in one repaint. No JS, no state, no flicker.
The Deploy Pipeline
The last piece: how the code in your editor becomes the HTML your browser sees. The cf:deploy script is two steps:
Figure 3 — The deploy pipeline. Local edits → next build prerenders static HTML for every page → OpenNext bundles everything into a single Worker + static assets package → wrangler deploy uploads to Cloudflare → every edge node serves it within seconds.
Three commands do the whole thing:
cd website-next
npm run cf:build # next build + opennextjs-cloudflare build
npm run cf:deploy # wrangler deploy the result
From "I just wrote a chapter" to "it's live in Tokyo, London, São Paulo, and Sydney" takes about two minutes. Cold-start on a previously-uncached edge the first time, ~30ms thereafter.
Patterns Worth Stealing
If you take three things away from this chapter:
- File-based content beats a CMS for tutorials, docs, and marketing sites. Git is your editor, your review system, your rollback mechanism, your schema migration tool. MDX + frontmatter is enough.
- MDX component overrides are a whole design language. Fifteen lines in a
componentsmap turns a plain code fence into a custom rendered block. Use them for callouts, diagrams, embeds, demos — anything you'd otherwise copy-paste. - Prerender everything possible, then edge-cache the result. The fastest page is the one your origin never sees. Next's
generateStaticParams+ Cloudflare'ss-maxageis the simplest way to ship genuinely global latency.
Part 1 Ends Here
You now know:
- The full request lifecycle of the web (Ch 1)
- HTML structure and the DOM (Ch 2)
- CSS selectors, the box model, layout, responsiveness (Ch 3)
- Design systems with tokens and primitives (Ch 4)
- The JavaScript you need for real UI code (Ch 5)
- How all five concepts compose inside a real Next.js + Cloudflare + MDX codebase (this chapter)
Part 2 (Ch 7-14) picks up where most frontend tutorials stop. We leave the browser and walk to the other end of the HTTP loop — building the servers the browser talks to. You'll write your first Cloudflare Worker in Ch 8, your first SQL schema in Ch 9, your first authenticated session in Ch 11, your first Stripe webhook in Ch 13, and in Ch 14 you'll dissect the actual backend API behind simpleappshipper.com — hand-rolled JWT, Google OAuth, Stripe, D1, R2 — the same pattern tree you just saw for the frontend.
See you in Chapter 7.
Ship your apps faster
When you're ready to publish your Swift app to the App Store, Simple App Shipper handles metadata, screenshots, TestFlight, and submissions — all in one place.
Try Simple App Shipper