This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ before writing any code. Heed deprecation notices.
Next.js 16 (App Router) · React 19 · Tailwind CSS v4 · TypeScript · pnpm. Node ≥24 (see .nvmrc).
pnpm install # required — husky hooks and lint-staged invoke `pnpm exec`
pnpm dev # dev server on :3000
pnpm build # static export → ./out (chains check:out + check:og-image)
pnpm start # serve a prior `next build` output (not the static export)
pnpm test # vitest run — `lib/blog.test.ts` for a pure-module test,
# `components/ui/MethodRow.test.ts` for a component one
pnpm typecheck # tsc --noEmit (CI) — `next build` only checks what it bundles
pnpm lint # eslint (flat config)
pnpm format # prettier --write .
pnpm format:check # prettier --check . (CI)
pnpm check:out # postbuild assertions against ./out (runs inside `pnpm build`)
pnpm check:og-image # postbuild social-card guard over ./out (runs inside `pnpm build`)app/— App Router entry (layout.tsx,page.tsx,globals.css), theblog/andblog/[slug]/routes, thelegal/[doc]route, therobots.txt/route.ts/sitemap.xml/route.ts/sitemap-pages.xml/route.ts/llms.txt/route.ts/llms-full.txt/route.tshandlers, and the file-convention assets (favicon.ico,icon.svg,apple-icon.png,opengraph-image.{png,alt.txt}).components/site/— page sections composed byapp/page.tsx(Hero, Compare, Method, Faq, Contact, …) plus chrome (Chrome,Footer,ScrollReveal, …). Section components are named after their sectionid(e.g.Contact.tsxforid="contact");Herois the idiomatic exception for the topid="intro"section.components/ui/— low-level primitives (Frame, SectionHeader, Prose, Figure, …).lib/— shared helpers (links.ts,blog.ts,faq.ts,legal.ts,jsonld.tsx,schema.ts, …).lib/schema.tsholds the site-level JSON-LD nodes; the per-postBlogPostingnode isblogPostingNodeinlib/blog.tsbecause it derives from a post rather than from the site —blogNodenests it andapp/blog/[slug]/page.tsxemits it top-level, so they cannot diverge under the same@id.lib/copy.ts— the site's prose, as plain data. New or edited homepage copy goes here, not inline in a component: the comparison table maps overCOMPARE_ROWSandapp/llms-full.txt/route.tsserialises the same strings as markdown, so copy inlined in JSX silently drifts from the mirror. This is not hypothetical — the hero and method stat strips were inline<Figure>attributes restated by hand in the mirror, and had already drifted. Values as well as sentences:HERO_FIGURES,STACK,PANEL_CAPTIONS. Render prose throughhighlightBrand(components/ui/brand.tsx) wherever the sentence names the product.lib/stats.ts— the live testnet figures behind the hero terminal and the Contact fleet panel. The page stays a static export: both panels render a loading state into the HTML and fill in after mount fromhttps://data.decdn.org/stats-421614.json, which thedecdn/statsindexer rewrites every five minutes (schema:worker/src/stats.tsthere). Shape check, formatting and view models are pure and unit-tested;lib/use-stats.tsis the one-fetch-per-page client hook. The CSP inpublic/_headersallowlists the origin underconnect-src— change both together.test-utils/— test-only helpers (react-tree.tswalks the element tree a server component returns). Nothing underapp/orcomponents/imports from here.scripts/—check-out.mjs, the postbuild assertionspnpm buildruns against./out.content/blog/— MDX posts loaded bylib/blog.tsand rendered viaapp/blog/[slug]/page.tsx.content/legal/— MDX for the legal pages (privacy,terms,disclaimer) loaded bylib/legal.tsand rendered viaapp/legal/[doc]/page.tsx.scripts/— build-time guards run against the export, not shipped with it (check-out.mjsfor advertised URLs and dotted routes,check-og-image.mjsfor social cards; both chained offpnpm build). Node built-ins only, no dependencies;scripts/*.test.tsspawns them against fixture exports.docs/— Mintlify source fordocs.decdn.org(separate build pipeline, not part of the static export).- Path alias
@/*→ project root (e.g.@/lib/links, not@/src/...).
- Static export only.
next.config.tshasoutput: "export". No SSR, ISR, middleware, or Image Optimization API. Route handlers (app/robots.txt/route.ts,app/sitemap.xml/route.ts,app/sitemap-pages.xml/route.ts,app/llms.txt/route.ts,app/llms-full.txt/route.ts) are allowed only when statically generated at build time (dynamic = "force-static", GET-only).robots.txtis a hand-written route handler rather than the Nextapp/robots.tsmetadata file so it can emit non-standard directives (Content-Signal:per contentsignals.org). A dotted route segment is what makes these land as real files (out/llms.txt) rather than<path>/index.htmldespitetrailingSlash: true. trailingSlash: true.next.config.tsemits every route as<path>/index.htmland canonical/internal links should expect a trailing slash. Cloudflare Pages servesout/as-is.- Tailwind v4.
globals.cssuses@import "tailwindcss"and@theme inline { … }. There is notailwind.config.*— theme tokens live in CSS. Don't reach for v3 directives. - Conventional commits required.
commitlintruns in thecommit-msghusky hook; non-conforming messages are rejected. metadataBaseis live.lib/links.tssiteis the real origin andINDEXABLEistrue. Anything anchored on this origin — OG and canonical (viametadataBase); JSON-LD,app/sitemap.xml/route.ts,app/sitemap-pages.xml/route.ts,app/robots.txt/route.ts(viaSITE_URL) — ships to production. Adding a new non-blog page = append an entry toapp/sitemap-pages.xml/route.ts; blog posts auto-derive fromcontent/blog/and legal pages from the closedLEGAL_SLUGSlist inlib/legal.ts. FlipINDEXABLEto mark pages noindex;robots.txtand the sitemap remain unchanged by design (seelib/links.tsfor why).app/llms.txt/route.tsandapp/llms-full.txt/route.tsship to the same origin and derive their page entries fromlistPosts()/LEGAL_SLUGS/lib/copy.ts; both are listed inapp/sitemap-pages.xml/route.tsand advertised viaalternates.typesinapp/layout.tsxand aLink:header inpublic/_headers. Deriving entries is not by itself a guarantee that a URL exists — the sitemap's two llms entries are literals, and the litepaper, press kit anddocs.decdn.orgentries are not routes at all.scripts/check-out.mjsis what resolves every advertised same-origin URL againstout/after a build;scripts/check-og-image.mjsdoes the same for everyog:image/twitter:imagein every built page, plus everysitemap-pages.xml<loc>.docs/is a different product. Mintlify Cloud builds it fromdocs/docs.jsonand serves it atdocs.decdn.org— independent ofpnpm build. The website code must not import fromdocs/; ESLint and the website CI workflow ignore it. Edits to MDX go through thedocsworkflow (Prettier +markdownlint-cli2+mintlify broken-links).- vitest only collects
**/\*.test.ts. A.test.tsxis silently skipped — the suite stays green and the file never runs. Component tests therefore contain no JSX: call the component as the plain function it is and walk the result withtest-utils/react-tree.ts(orReact.createElementfor fixtures). - CI typechecks separately.
next buildonly typechecks what it bundles, so test files are invisible to it — one had a type error onmain.pnpm typecheckis a distinct CI step. - CSS custom properties in
styleprops. Known--varnames are declared intypes/css.d.ts(module-augmenting React'sCSSProperties) so call sites can writestyle={{ "--reveal-delay": "120ms" }}without a cast. Add new vars there before using them.