New Page
A new ethereum.org page is reuse-first: every region maps to a layout, hero, primitive, or variant that already exists — you configure what's there, you don't build a parallel version of it. This skill interviews you for the missing context, then scaffolds the page against the design system.
Work the steps in order. Each ends on a checkable condition — don't start the next until the current one is met.
Step 0 — Load the design system
Invoke the design-system skill and read its SKILL.md fully before asking anything else. It is the source of truth for component choices, tokens, layouts, RTL/i18n, and the server/client boundary. Do not restate its catalog here — when you need the layout inventory read design-system/references/layouts.md, and verify it against src/layouts/index.ts (layouts get refactored).
Done when: design-system SKILL.md has been read this session.
Step 1 — Interview
The primary fork is markdown-backed or not. It decides the layout system, the build, and how the page is translated — so settle it first. It is an authoring decision (how the content is written and maintained), not a visual one: a hero-topped prose page can be either, because ContentLayout gives markdown TopicLayout and App Router pages the same hero + card-TOC chrome. So ask it; never infer it from the design.
Use AskUserQuestion:
- Markdown page or App Router page?
- Markdown page — content authored in
public/content/<route>/index.md, rendered through[...slug], translated by the intl-pipeline, built from a fixed set of MDX shortcodes. Choose for editorial/educational content a writer maintains in markdown. - App Router page — a hand-built
app/[locale]/<route>/page.tsxthat composesContentLayout. Choose when the page is interactive, data-backed, or needs layout/components the markdown shortcodes can't express. Translated viagetTranslationsJSON keys.
- Markdown page — content authored in
- Route — the URL path (e.g.
/stablecoins/). The folder name is the route. - Design source — a Figma link/selection, a written description, or none.
- Content (markdown only) — does the user have the body ready, or should you draft it?
Then check the route is free: no existing public/content/<route>/index.md or app/[locale]/<route>/page.tsx. If one exists you're editing, not creating — surface it and confirm before any write; never silently overwrite a page you didn't author.
Done when: markdown-vs-App-Router, route, and design source are known, and the route is confirmed free (or editing is okayed).
Step 2 — Pull the design (only if Figma was provided)
Read the design fresh with the Figma MCP (designs change between runs — never rely on a read from an earlier turn): get_metadata for the frame tree, then get_design_context / get_screenshot on each region; search_design_system / get_code_connect_map for existing code mappings. Load the figma-use skill before any use_figma call.
Produce a component map and always show it to the user as a table — three columns: Region (each design region, top to bottom) · Maps to (the existing primitive + variant that renders it — PageHero, Card, BigNumber, …) · Status (reuse, missing asset, placeholder copy, missing data, …). Walk the frame top to bottom and map every region. The table is the user's only window into what you saw in the frame — render it every run; never just reason about the mapping internally.
Node names lie. A node called Screenshot 2026-… or Frame 1789 is routinely real content — a stat band (BigNumber), a video embed (YouTube), a callout (Alert) — not a throwaway. Never omit a region because its name looks like a draft; get_screenshot it and see what it actually is before deciding.
Missing data → ask, don't guess. When a region needs a value the design doesn't carry — a YouTube ID behind a thumbnail, a real destination URL, a live data source — you can't extract it from the frame. Flag it and ask the user; a guessed embed or invented link is worse than an open question.
Match, don't mimic. Reproduce the design with existing components and their variants. Do not layer custom Tailwind/CSS on a primitive to chase pixel parity. If a region has no fitting primitive or variant, the answer is add a variant (design-system/references/variant-vs-new.md) — never a one-off component or bespoke styles. Flag any such gap and confirm before inventing.
Pressure-test the authoring fork. The map can reveal a region markdown shortcodes can't express — an interactive widget, a data-backed list, a bespoke multi-column grid, or cards with lucide icons (MarkdownCard accepts emoji or icon: ReactNode, but a plain .md file can't author a ReactNode, so from markdown the <Card> shortcode is effectively emoji-only). Any of these means the page wants to be App Router. If Step 1 said markdown, surface the conflict and re-confirm before scaffolding — flipping it later is a rewrite.
Done when: the component-map table has been shown to the user and they've confirmed or corrected it; every region is accounted for — mapped, flagged for an approved variant, or raised as missing data — and the authoring fork still holds.
Step 3 — Pick the layout and hero
Branches on Step 1.
Markdown page — pick the template:
The layout is selected by the template: frontmatter value (default static). Full inventory in design-system/references/layouts.md; live set in src/layouts/index.ts.
template: |
Layout | Use for | Hero it renders |
|---|---|---|---|
static (default) |
StaticLayout |
one-off editorial prose, no sub-nav | title + breadcrumbs only (PageHero variant="no-divider", no image) |
use-cases / staking / roadmap / upgrade |
TopicLayout |
topic hub: full side-image hero + sibling sub-nav | image PageHero from frontmatter image / summary / buttons |
docs |
DocsLayout |
developer docs with the docs sidebar | — |
tutorial |
TutorialLayout |
developer tutorial with author/date/skill metadata | — |
- Each Topic template carries a distinct MDX component list (
componentsMapping,src/layouts/index.ts) — pick the one whose shortcodes match the content. - Side-image hero in the design → a Topic template (
staticrenders only a title hero).showDropdown, new-topic wiring, and the never-add-a-new-layout rule:design-system/references/layouts.md.
App Router page — compose ContentLayout, pass the hero that fits
Hand-build app/[locale]/<route>/page.tsx composing ContentLayout, and choose the hero from the design:
PageHero— breadcrumbs + title + optionalheroImg+ description + buttons. The workhorse; covers both image and title-only heroes.HubHero— large hub hero (exemplar:/learn/).
Build the tocItems array by hand and wrap each prose section in <Section id>. ContentLayout owns the card TOC, contributors block, and feedback. Exemplars: /learn/, /what-is-ethereum/.
Done when: markdown → template: (and showDropdown if relevant) chosen; App Router → hero component chosen. Confirmed with the user.
Step 4 — Scaffold
Build from the Step 2 component map. Reuse-first throughout — no raw <a>/<button>, no hex colors, logical CSS props (ms-/me-/…), locale-aware numberFormat()/dateTimeFormat(). Icons: lucide in .tsx, emoji in markdown (markdown can't pass the icon ReactNode prop).
Markdown page:
- Create
public/content/<route>/index.mdwith frontmatter (title,description,lang: en, plustemplate:unless static). English only — never hand-write translated copies; the intl-pipeline propagates them. - Inline images co-locate next to
index.md, referenced relatively — a/images/...inline path 500s the MDX render; the frontmatterimage:hero is the exception. Details:design-system/references/gotchas.md. - Every h1–h4 needs a
{#kebab-id}; runpnpm lint:md:fix. - If using
TopicLayout: addsrc/data/topics/<key>.ts, wirelayoutMapping/componentsMappinginsrc/layouts/index.ts, and addsrc/intl/en/page-<key>.json.
App Router page:
- Create
app/[locale]/<route>/page.tsxcomposingContentLayout(mirror/learn/or/what-is-ethereum/): passheroSection, the hand-builttocItems, andcontributors; wrap prose in<Section id>. Server Component unless it needs state/effects/handlers. - All user-facing strings via
getTranslations; add keys tosrc/intl/en/.
Done when: the page renders through its layout with content/strings in place and no inlined reinventions of existing primitives.
Step 5 — Verify (static checks)
pnpm type-check(catches invalid chain names and type errors).- Walk the design-system Pre-Merge Smoke Test checklist.
- Add a
.stories.tsxfor any genuinely new UI primitive.
Done when: type-check passes and the smoke checklist is clean.
Step 6 — QA via the adversarial review loop
Static checks pass on pages that are visibly broken — a wrong image path, a failed MDX compile, a section that silently didn't render. So the page must be run and audited via the adversarial reviewer loop, not an eyeball comparison.
- Run it. Start
pnpm dev, wait for "Ready", load the page (locale-stripped foren:/<route>/). Confirm HTTP 200 — a 500 is almost always an MDX or asset error; read the dev log (a non-co-located inline image is the classic one). There is nothing to review on a broken page. - Run the review loop. Read
review-page/SKILL.mdand execute its Steps 1–5 against this route until green. You already hold the design source and route from Steps 1–2, so its Step 0 (running page + known design source) is already satisfied. Read and run that procedure; don't try to invoke/review-pageas a skill from here.- A hero finding is a template bug, not a CSS gap. When the design-system reviewer flags a title-only hero against a side-image design: on a markdown page that's a wrong-
template:(use a Topic template, notstatic); on an App Router page, passheroImgtoPageHero. Never rebuild a hero with custom markup.
- A hero finding is a template bug, not a CSS gap. When the design-system reviewer flags a title-only hero against a side-image design: on a markdown page that's a wrong-
- Hand off. Leave the dev server running and give the user the local URL the server printed (e.g.
http://localhost:3000/<route>/).
Done when: the review loop is green (or hits its round cap with the remaining findings handed off), and the user has the live URL.