From 2eb15b7fcc121f0d11a1f82b8bb1cf7b9ba329dc Mon Sep 17 00:00:00 2001 From: Vivek Date: Sat, 25 Jul 2026 10:04:05 +0530 Subject: [PATCH 1/3] feat(website): render site footer from the root layout on every page The footer was rendered inline only by the home page and /why, so /blog, /compare, /changelog, /articles, and every detail page had none. Move the siteFooter() call into app/layout.ts (alongside the shared header/nav chrome) and drop the per-page calls, so every route gets one footer with no duplication. Pin it with a layout SSR test that composes a bare child. --- website/AGENTS.md | 5 +++-- website/app/layout.ts | 3 +++ website/app/page.ts | 3 --- website/app/why/page.ts | 3 --- website/lib/site-footer.ts | 9 +++++---- website/test/ssr/layout-ssr.test.ts | 12 ++++++++++++ 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/website/AGENTS.md b/website/AGENTS.md index b216e01f9..04d368430 100644 --- a/website/AGENTS.md +++ b/website/AGENTS.md @@ -16,8 +16,9 @@ website/ layout.ts root layout (head, OG/Twitter metadata, header/footer chrome, Tailwind tokens) page.ts / → the entire one-page landing site. - Hero, features grid, code samples, agent - badges, and footer all live here. + Hero, features grid, code samples, and agent + badges all live here. The header and footer + chrome are in layout.ts (shared by every page). changelog/page.ts /changelog. Reads ../../../changelog//*.md at SSR time and renders the unified release feed. The deployment image must include the diff --git a/website/app/layout.ts b/website/app/layout.ts index 3c91fb4b1..bbc6a044b 100644 --- a/website/app/layout.ts +++ b/website/app/layout.ts @@ -1,6 +1,7 @@ import { html, cspNonce } from '@webjsdev/core'; import '#components/theme-toggle.ts'; import { DOCS_URL, UI_URL, EXAMPLE_BLOG_URL, GH_URL, NEW_TAB } from '#lib/links.ts'; +import { siteFooter } from '#lib/site-footer.ts'; /** * Root layout for the redesigned marketing site. @@ -288,6 +289,8 @@ export default function RootLayout({ children }: { children: unknown }) {
${children} + + ${siteFooter()}
`; } diff --git a/website/app/page.ts b/website/app/page.ts index 71ed19c02..c0ebf3c6f 100644 --- a/website/app/page.ts +++ b/website/app/page.ts @@ -3,7 +3,6 @@ import '#components/copy-cmd.ts'; import '#components/like-button.ts'; import { COMPONENT_SAMPLE, ACTION_SAMPLE, PAGE_SAMPLE } from '#lib/samples.ts'; import { DOCS_URL, GH_URL, NEW_TAB } from '#lib/links.ts'; -import { siteFooter } from '#lib/site-footer.ts'; // highlight() runs only at SSR (codeWindow renders its output into the served // HTML), but it does ship to the client as a small dead module: the page loads // in the browser to register copy-cmd, and that pulls in its @@ -400,7 +399,5 @@ middleware.ts - - ${siteFooter()} `; } diff --git a/website/app/why/page.ts b/website/app/why/page.ts index 56fcea097..581163900 100644 --- a/website/app/why/page.ts +++ b/website/app/why/page.ts @@ -1,7 +1,6 @@ import { html } from '@webjsdev/core'; import '#components/copy-cmd.ts'; import { DOCS_URL, GH_URL, EXAMPLE_BLOG_URL, NEW_TAB } from '#lib/links.ts'; -import { siteFooter } from '#lib/site-footer.ts'; /** * /why @@ -212,7 +211,5 @@ Counter.register('counter'); - - ${siteFooter()} `; } diff --git a/website/lib/site-footer.ts b/website/lib/site-footer.ts index cd6b66342..01d5bb53a 100644 --- a/website/lib/site-footer.ts +++ b/website/lib/site-footer.ts @@ -2,11 +2,12 @@ import { html } from '@webjsdev/core'; import { DOCS_URL, UI_URL, EXAMPLE_BLOG_URL, GH_URL, DISCORD_URL, NEW_TAB } from '#lib/links.ts'; /** - * The site-wide footer, shared across marketing pages (the home page and /why). + * The site-wide footer, rendered once by the root layout (app/layout.ts) so it + * appears on every page, the same way the header/nav does. * - * It lives here rather than inline in a page so every page renders the same - * chrome. Pure SSR-time helper: it returns an `html` fragment and touches no - * client globals, so importing it never ships a page to the browser. + * It lives here rather than inline in the layout so the chrome stays readable. + * Pure SSR-time helper: it returns an `html` fragment and touches no client + * globals, so importing it never ships a page to the browser. * * Anchor links point at `/#` (not a bare `#`) so a section anchor * resolves from any page, not only the home page. diff --git a/website/test/ssr/layout-ssr.test.ts b/website/test/ssr/layout-ssr.test.ts index d2ae7a417..9069f725c 100644 --- a/website/test/ssr/layout-ssr.test.ts +++ b/website/test/ssr/layout-ssr.test.ts @@ -54,6 +54,18 @@ test('every nav landmark carries a distinguishing aria-label', async () => { assert.ok(labels.includes('Footer'), 'the footer nav is labeled Footer'); }); +test('the root layout renders the site footer around a bare child (every page gets it)', async () => { + // The footer is layout chrome, not per-page markup, so a page that renders + // only its own
(blog, compare, changelog, articles) still gets exactly + // one footer. Compose the layout around a minimal child and assert the footer + // nav is present and appears once. Guards against the footer regressing back + // into individual pages (where blog/compare/changelog silently lost it). + const out = await renderToString(RootLayout({ children: html`
content
` })); + const footers = (out.match(/ { const out = await renderToString(RootLayout({ children: LandingPage() })); // Every target="_blank" link carries a visually-hidden new-tab cue. From 4623a6630f530d80a9940a701f034bb5a6185623 Mon Sep 17 00:00:00 2001 From: Vivek Date: Sat, 25 Jul 2026 11:49:47 +0530 Subject: [PATCH 2/3] fix(website): make compare card eyebrow neutral, not amber Each /compare card led with an amber "WebJs vs X" eyebrow, competing with the page's own amber "Compare" header for emphasis. Make the per-card eyebrow neutral (text-fg-subtle, matching the blog card's date/tag meta) so the amber accent is reserved for the single page header. --- website/app/compare/page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/app/compare/page.ts b/website/app/compare/page.ts index c1752daf9..3ccb031d1 100644 --- a/website/app/compare/page.ts +++ b/website/app/compare/page.ts @@ -33,7 +33,7 @@ export default async function Compare() {
- WebJs vs ${c.competitor} + WebJs vs ${c.competitor}

${c.tagline}

${c.description}

From b7d8819311454225a11ba288022842cd6ad2a24a Mon Sep 17 00:00:00 2001 From: Vivek Date: Sat, 25 Jul 2026 11:50:31 +0530 Subject: [PATCH 3/3] style(website): use z-1 dynamic utility over z-[1] arbitrary value --- website/app/layout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/app/layout.ts b/website/app/layout.ts index bbc6a044b..2f7c601c9 100644 --- a/website/app/layout.ts +++ b/website/app/layout.ts @@ -287,7 +287,7 @@ export default function RootLayout({ children }: { children: unknown }) { -
+
${children} ${siteFooter()}