-
Notifications
You must be signed in to change notification settings - Fork 5.4k
feat(layouts): consolidate topic page layouts #18198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
cba15c3
feat(layouts): consolidate topic layouts
myelinated-wackerow 5c2e33b
refactor(content): migrate summary points
myelinated-wackerow ba00561
feat(md): render fenced code via Codeblock
myelinated-wackerow 4f898f9
docs(skills): land TopicLayout guidance
myelinated-wackerow 540aea8
Merge branch 'dev' into feat/topic-layout-refactor
myelinated-wackerow 6d68fb3
refactor(layouts): drop pass-through slot props
myelinated-wackerow 3545d00
patch: remove unavailable export
myelinated-wackerow c930c4c
fix(layouts): derive hero dims from source
myelinated-wackerow File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| # Layouts | ||
|
|
||
| > **TL;DR**: Creating a new layout is a very rare exception. There are six canonical layouts and you almost never need a seventh. Reach for a `TopicLayout` config or a small slot prop before opening a new layout file. | ||
|
|
||
| ## The Canonical Layouts | ||
|
|
||
| The site has **six** layouts. All live in `src/layouts/`. Each maps to a `template:` value in markdown frontmatter via `layoutMapping` (`src/layouts/index.ts`). | ||
|
|
||
| | Layout | File | When to use | `template:` values it serves | | ||
| |---|---|---|---| | ||
| | `TopicLayout` | `src/layouts/Topic.tsx` | A topic hub with a shared sub-nav dropdown linking sibling pages. The workhorse for sectioned educational content. | `staking`, `use-cases`, `roadmap`, `upgrade`, `ai-agents` | | ||
| | `StaticLayout` | `src/layouts/Static.tsx` | One-off markdown pages with no sub-nav. | `static` (default fallback) | | ||
| | `DocsLayout` | `src/layouts/Docs.tsx` | Developer docs with the docs sidebar. | `docs` | | ||
| | `TutorialLayout` | `src/layouts/Tutorial.tsx` | Long-form developer tutorials with author/date/skill metadata. | `tutorial` | | ||
| | `ContentLayout` | `src/layouts/ContentLayout.tsx` | **Not a top-level layout.** Underlying scaffold consumed by the four above plus a handful of app-router pages (e.g. `/learn/`). | n/a (composed, not selected) | | ||
| | `BaseLayout` | `src/layouts/BaseLayout.tsx` | Root document scaffold (`<html>`, providers). Applied automatically by the App Router. | n/a | | ||
|
|
||
| That's the whole inventory. If a UI need can be met by configuring one of these (especially `TopicLayout`), it should be. | ||
|
|
||
| ## The Habit: Configure, Don't Add a New Layout | ||
|
|
||
| When you have a page or section that "needs its own layout," walk this list top-to-bottom and stop at the first match: | ||
|
|
||
| 1. **Does the page render through `[...slug]` with a markdown source?** Pick the right `template:` value. `TopicLayout` for anything with a sub-nav across sibling pages, `static` otherwise. No new layout. | ||
| 2. **Does the page have a sub-nav dropdown linking related sibling pages?** That's exactly what `TopicLayout` is for. Add a `src/data/topics/<key>.ts` config file. Route `layoutMapping[<key>] = TopicLayout`. No new layout. | ||
| 3. **Is the variation just a swap-in component (hero, before-content, after-content)?** Use the slots `TopicLayout` already exposes (`afterContent`, `heroSection` override via config `hubHero`) or add a narrow new slot. No new layout. | ||
| 4. **Is the page a one-off App Router page (`app/[locale]/<route>/page.tsx`) with non-markdown content?** Compose `ContentLayout` directly (see `/learn/`). No new layout. | ||
|
|
||
| If you can stop at any of those steps, you don't need a new layout file. | ||
|
|
||
| ## `TopicLayout` in Practice | ||
|
|
||
| `TopicLayout` is the canonical "topic hub" layout. It renders a hero, a TOC, content, a contributors block, a feedback card, and a sub-nav dropdown linking sibling pages. Everything per-topic comes from data. | ||
|
|
||
| ### Adding a new topic | ||
|
|
||
| To add a new topic-style section (e.g. a new `developer-platforms` hub), you need **two** changes — no React layout file required: | ||
|
|
||
| #### 1. Create the topic config | ||
|
|
||
| ```ts | ||
| // src/data/topics/developer-platforms.ts | ||
| import type { TopicConfig } from "." | ||
|
|
||
| export const developerPlatforms: TopicConfig = { | ||
| translationNs: "page-developer-platforms", | ||
| dropdown: { | ||
| textKey: "page-developer-platforms-dropdown", | ||
| ariaLabelKey: "page-developer-platforms-dropdown-aria", | ||
| matomoCategory: "developer platforms menu", | ||
| items: [ | ||
| { textKey: "page-developer-platforms-dropdown-home", href: "/developer-platforms/", matomoEvent: "home" }, | ||
| { textKey: "page-developer-platforms-dropdown-tools", href: "/developer-platforms/tools/", matomoEvent: "tools" }, | ||
| { textKey: "page-developer-platforms-dropdown-frameworks", href: "/developer-platforms/frameworks/", matomoEvent: "frameworks" }, | ||
| ], | ||
| }, | ||
| } | ||
| ``` | ||
|
|
||
| #### 2. Wire it into the map | ||
|
|
||
| ```ts | ||
| // src/data/topics/index.ts | ||
| import { developerPlatforms } from "./developer-platforms" | ||
|
|
||
| export const topics: Partial<Record<Layout, TopicConfig>> = { | ||
| // ... | ||
| "developer-platforms": developerPlatforms, | ||
| } | ||
|
|
||
| // src/layouts/index.ts | ||
| export const layoutMapping = { | ||
| // ... | ||
| "developer-platforms": TopicLayout, | ||
| } | ||
| ``` | ||
|
|
||
| #### 3. Add translation keys | ||
|
|
||
| In `src/intl/en/page-developer-platforms.json`. The intl-pipeline propagates to other locales. | ||
|
|
||
| That's it. No new layout component. No new MDX wiring beyond a `componentsMapping` entry (only if the section needs custom MDX components — most don't). | ||
|
|
||
| ### Slots on `TopicLayout` | ||
|
|
||
| When the topic genuinely needs something extra: | ||
|
|
||
| - **`config.hubHero`** — Swap `ContentHero` for `HubHero` on a specific slug (used by Roadmap on `/roadmap/`). Declarative; lives in the topic config. | ||
| - **`config.editBanner`** — Render the top-of-page "edit this page" banner on every page in the topic. Used by UseCases and AiAgents. Per-page opt-out via frontmatter `hideEditBanner: true` if a specific page needs to suppress. | ||
| - **`afterContent` prop** — Render arbitrary JSX after the markdown content. Used by Staking for its community callout. Passed by the slug router for the one or two topics that need it. If you find yourself wanting a *third* `afterContent` consumer, consider promoting it to `config.afterContent` (still keyed by topic data). | ||
|
|
||
| If your topic needs something none of these expose, the right move is usually a narrow new slot on `TopicLayout`, not a new layout file. | ||
|
|
||
| ## When a New Layout IS the Answer | ||
|
|
||
| The bar is high. A new layout is justified only when: | ||
|
|
||
| - The page renders through a fundamentally different content shape (e.g. a JSON-API-backed page vs markdown-content) | ||
| - It has a different navigation chrome (e.g. the docs sidebar, the tutorial metadata block) that doesn't fit `ContentLayout`'s scaffolding | ||
| - It manages a different content lifecycle (e.g. live data, streamed responses) | ||
|
|
||
| Cosmetic variation, different copy, a different sub-nav list, or a different hero image is **never** justification for a new layout. Those are all configuration of an existing layout. | ||
|
|
||
| ## When You Find a One-Off Layout File | ||
|
|
||
| If you encounter a `src/layouts/md/<Something>.tsx` that exports its own `<Something>Layout` component (this is the pattern the topic refactor cleaned up), it's a cleanup target: | ||
|
|
||
| 1. Confirm it's a topic-hub-with-sub-nav pattern (it almost always is) | ||
| 2. Extract the dropdown items + translation namespace into `src/data/topics/<key>.ts` | ||
| 3. Route `layoutMapping[<key>] = TopicLayout` | ||
| 4. Delete the layout export from the file; keep the MDX components export | ||
| 5. Smoke the section's pages | ||
|
|
||
| See `docs/topic-layout-refactor.md` for the worked example. | ||
|
|
||
| ## Pre-Merge Checklist for Layout Work | ||
|
|
||
| Before opening a PR that touches anything in `src/layouts/`: | ||
|
|
||
| - [ ] Am I sure this isn't a `TopicLayout` config addition? | ||
| - [ ] Am I sure this isn't a slot/prop addition to an existing layout? | ||
| - [ ] Have I checked the `layoutMapping` to confirm no existing layout fits? | ||
| - [ ] Have I read `docs/topic-layout-refactor.md` for context on why the topic layouts were consolidated? | ||
| - [ ] If introducing a new layout (very rare), do I have explicit signoff from a maintainer? | ||
| - [ ] If extending `ContentLayout`, is the new prop genuinely shared across multiple consumers — not a one-section special case? | ||
|
|
||
| If you can't say yes to all of these and you're about to add `src/layouts/<NewName>.tsx`, stop and re-read the top of this file. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,18 +10,20 @@ import type { GHIssue, SlugPageParams } from "@/lib/types" | |||||||
|
|
||||||||
| import I18nProvider from "@/components/I18nProvider" | ||||||||
| import mdComponents from "@/components/MdComponents" | ||||||||
| import StakingCommunityCallout from "@/components/Staking/StakingCommunityCallout" | ||||||||
| import VideoWatch from "@/components/Videos/VideoWatch" | ||||||||
|
|
||||||||
| import { dateToString } from "@/lib/utils/date" | ||||||||
| import { getLayoutFromSlug } from "@/lib/utils/layout" | ||||||||
| import { checkPathValidity, getPostSlugs } from "@/lib/utils/md" | ||||||||
| import { getRequiredNamespacesForPage } from "@/lib/utils/translations" | ||||||||
|
|
||||||||
| import { topics } from "@/data/topics" | ||||||||
| import { getGFIs } from "@/data-layer" | ||||||||
|
|
||||||||
| import SlugJsonLD from "./page-jsonld" | ||||||||
|
|
||||||||
| import { componentsMapping, layoutMapping } from "@/layouts" | ||||||||
| import { componentsMapping, layoutMapping, TopicLayout } from "@/layouts" | ||||||||
| import { getPageData } from "@/lib/md/data" | ||||||||
| import { getMdMetadata } from "@/lib/md/metadata" | ||||||||
|
|
||||||||
|
|
@@ -67,7 +69,7 @@ export default async function Page(props: { params: Promise<SlugPageParams> }) { | |||||||
|
|
||||||||
| // Determine the actual layout after we have the frontmatter | ||||||||
| const layout = frontmatter.template || getLayoutFromSlug(slug) | ||||||||
| const Layout = layoutMapping[layout] | ||||||||
| const topicConfig = topics[layout] | ||||||||
|
|
||||||||
| // If the page has a published date, format it | ||||||||
| if ("published" in frontmatter) { | ||||||||
|
|
@@ -79,6 +81,40 @@ export default async function Page(props: { params: Promise<SlugPageParams> }) { | |||||||
| const requiredNamespaces = getRequiredNamespacesForPage(slug, layout) | ||||||||
| const messages = pick(allMessages, requiredNamespaces) | ||||||||
|
|
||||||||
| if (topicConfig) { | ||||||||
| const afterContent = | ||||||||
| layout === "staking" ? ( | ||||||||
| <StakingCommunityCallout className="my-16" /> | ||||||||
| ) : undefined | ||||||||
|
|
||||||||
| return ( | ||||||||
| <> | ||||||||
| <SlugJsonLD | ||||||||
| locale={locale} | ||||||||
| slug={slug} | ||||||||
| frontmatter={frontmatter} | ||||||||
| contributors={contributors} | ||||||||
| /> | ||||||||
| <I18nProvider locale={locale} messages={messages}> | ||||||||
| <TopicLayout | ||||||||
| slug={slug} | ||||||||
| frontmatter={frontmatter} | ||||||||
| tocItems={tocItems} | ||||||||
| lastEditLocaleTimestamp={lastEditLocaleTimestamp} | ||||||||
| contentNotTranslated={!isTranslated} | ||||||||
| contributors={contributors} | ||||||||
| config={topicConfig} | ||||||||
| > | ||||||||
| {content} | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of adding a new prop
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree -- patched |
||||||||
| {afterContent} | ||||||||
| </TopicLayout> | ||||||||
| </I18nProvider> | ||||||||
| </> | ||||||||
| ) | ||||||||
| } | ||||||||
|
|
||||||||
| const Layout = layoutMapping[layout] | ||||||||
|
|
||||||||
| return ( | ||||||||
| <> | ||||||||
| <SlugJsonLD | ||||||||
|
|
||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sidecomment, we should probably need to think about a global callout component (in base layout perhaps) to be used for any page that needs something like this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree -- can separate this task