Skip to content

Commit 308c175

Browse files
bloveclaude
andcommitted
refactor(website): drop the marketing footer from the docs tree
Every /docs route now wears the sidebar control plane, and a marketing footer bolted under the article column breaks that single-pane read. The footer is mounted once in the root layout, so a route gate is its only opt-out: SiteFooter suppresses it on /docs and /docs/*, and every other route is untouched. Verified against the prerendered build: all 122 docs pages ship without footer-root, every other page still has it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent f927124 commit 308c175

3 files changed

Lines changed: 63 additions & 2 deletions

File tree

apps/website/src/app/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Metadata } from 'next';
22
import { EB_Garamond, Inter, JetBrains_Mono } from 'next/font/google';
33
import './global.css';
44
import { Nav } from '../components/shared/Nav';
5-
import { Footer } from '../components/shared/Footer';
5+
import { SiteFooter } from '../components/shared/SiteFooter';
66
import { AnnouncementToast } from '../components/shared/AnnouncementToast';
77
import { JsonLd } from '../components/shared/JsonLd';
88
import { rootJsonLd } from '../lib/structured-data';
@@ -70,7 +70,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
7070
<Nav />
7171
<div id="site-content">
7272
<main>{children}</main>
73-
<Footer />
73+
<SiteFooter />
7474
<AnnouncementToast />
7575
</div>
7676
</body>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// @vitest-environment jsdom
2+
import React from 'react';
3+
import { render } from '@testing-library/react';
4+
import { afterEach, describe, expect, it, vi } from 'vitest';
5+
6+
const pathname = vi.hoisted(() => ({ current: '/' }));
7+
8+
vi.mock('next/navigation', () => ({
9+
usePathname: () => pathname.current,
10+
}));
11+
12+
import { SiteFooter } from './SiteFooter';
13+
14+
afterEach(() => {
15+
pathname.current = '/';
16+
});
17+
18+
/**
19+
* The footer is mounted once, in the root layout, so it is the docs shell's
20+
* only way to opt out of it. These cases are the contract that opt-out obeys.
21+
*/
22+
describe('SiteFooter', () => {
23+
it.each(['/', '/pricing', '/blog/some-post', '/docs-adjacent'])(
24+
'renders the marketing footer on %s',
25+
(route) => {
26+
pathname.current = route;
27+
28+
const { container } = render(<SiteFooter />);
29+
30+
expect(container.querySelector('footer')).toBeTruthy();
31+
},
32+
);
33+
34+
it.each(['/docs', '/docs/choosing-an-adapter', '/docs/langgraph/guides/testing'])(
35+
'suppresses it on %s so the docs stay a single sidebar pane',
36+
(route) => {
37+
pathname.current = route;
38+
39+
const { container } = render(<SiteFooter />);
40+
41+
expect(container.querySelector('footer')).toBeNull();
42+
},
43+
);
44+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use client';
2+
import { usePathname } from 'next/navigation';
3+
import { Footer } from './Footer';
4+
5+
/**
6+
* Route gate for the marketing footer.
7+
*
8+
* The footer is mounted once in the root layout, so every route gets it unless
9+
* something opts out here. The docs tree opts out: every /docs route renders
10+
* the sidebar control plane and ends at its own prev/next rail, and a marketing
11+
* footer bolted under that column breaks the single-pane reading experience.
12+
*/
13+
export function SiteFooter() {
14+
const pathname = usePathname();
15+
if (pathname === '/docs' || pathname?.startsWith('/docs/')) return null;
16+
return <Footer />;
17+
}

0 commit comments

Comments
 (0)