feat(docs): add prev/next page navigation to doc pages

Add card-style previous/next links at the bottom of documentation pages using the sidebar navigation order from nav.ts. Includes section labels, arrow icons, hover effects, and dark mode support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Yechan Kim committed Apr 12, 2026 at 16:43 UTC 52d9372cbbebedf696d86ecf5e18486807562d77
3 files changed +102
docs/src/lib/components/PrevNextNav.svelte new
+71
@@ -0,0 +1,71 @@
1 +<script lang="ts">
2 + import { base } from '$app/paths';
3 + import type { FlatNavItem } from '$lib/nav';
4 +
5 + interface Props {
6 + prev: FlatNavItem | null;
7 + next: FlatNavItem | null;
8 + }
9 +
10 + let { prev, next }: Props = $props();
11 +</script>
12 +
13 +{#if prev || next}
14 + <nav class="not-prose mt-12 grid grid-cols-1 gap-4 sm:grid-cols-2" aria-label="Page navigation">
15 + {#if prev}
16 + <a
17 + href="{base}{prev.href}"
18 + class="group flex items-center gap-4 rounded-lg border border-border/70 px-5 py-4 transition-colors hover:border-primary/40"
19 + >
20 + <svg
21 + class="size-5 shrink-0 text-text-muted transition-transform group-hover:-translate-x-0.5"
22 + xmlns="http://www.w3.org/2000/svg"
23 + fill="none"
24 + viewBox="0 0 24 24"
25 + stroke-width="2"
26 + stroke="currentColor"
27 + aria-hidden="true"
28 + >
29 + <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
30 + </svg>
31 + <div class="min-w-0">
32 + <span class="block text-xs font-medium text-text-muted">Previous</span>
33 + <span class="block text-xs text-text-muted">{prev.section}</span>
34 + <span class="block truncate font-display text-sm font-semibold text-foreground">
35 + {prev.title}
36 + </span>
37 + </div>
38 + </a>
39 + {:else}
40 + <div class="hidden sm:block"></div>
41 + {/if}
42 +
43 + {#if next}
44 + <a
45 + href="{base}{next.href}"
46 + class="group flex items-center justify-end gap-4 rounded-lg border border-border/70 px-5 py-4 text-right transition-colors hover:border-primary/40"
47 + >
48 + <div class="min-w-0">
49 + <span class="block text-xs font-medium text-text-muted">Next</span>
50 + <span class="block text-xs text-text-muted">{next.section}</span>
51 + <span class="block truncate font-display text-sm font-semibold text-foreground">
52 + {next.title}
53 + </span>
54 + </div>
55 + <svg
56 + class="size-5 shrink-0 text-text-muted transition-transform group-hover:translate-x-0.5"
57 + xmlns="http://www.w3.org/2000/svg"
58 + fill="none"
59 + viewBox="0 0 24 24"
60 + stroke-width="2"
61 + stroke="currentColor"
62 + aria-hidden="true"
63 + >
64 + <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
65 + </svg>
66 + </a>
67 + {:else}
68 + <div></div>
69 + {/if}
70 + </nav>
71 +{/if}
docs/src/lib/nav.ts
+24
@@ -9,6 +9,30 @@ export interface NavSection {
9 items: NavItem[];
10 }
11
12 +export interface FlatNavItem extends NavItem {
13 + section: string;
14 +}
15 +
16 +export function flattenNavigation(): FlatNavItem[] {
17 + return navigation.flatMap((s) => s.items.map((item) => ({ ...item, section: s.title })));
18 +}
19 +
20 +export function getPrevNext(
21 + pathname: string,
22 + basePath: string
23 +): { prev: FlatNavItem | null; next: FlatNavItem | null } {
24 + const items = flattenNavigation();
25 + const index = items.findIndex(
26 + (item) =>
27 + pathname === `${basePath}${item.href}/` || pathname === `${basePath}${item.href}`
28 + );
29 + if (index === -1) return { prev: null, next: null };
30 + return {
31 + prev: index > 0 ? items[index - 1] : null,
32 + next: index < items.length - 1 ? items[index + 1] : null
33 + };
34 +}
35 +
36 export const navigation: NavSection[] = [
37 {
38 title: 'Getting Started',
docs/src/routes/+layout.svelte
+7
@@ -6,6 +6,8 @@
6 import ThemeToggle from '$lib/components/ThemeToggle.svelte';
7 import MobileNav from '$lib/components/MobileNav.svelte';
8 import TableOfContents from '$lib/components/TableOfContents.svelte';
9 + import PrevNextNav from '$lib/components/PrevNextNav.svelte';
10 + import { getPrevNext } from '$lib/nav';
11 import '../app.css';
12
13 let { children } = $props();
@@ -14,6 +16,8 @@
16 const isLandingPage = $derived(
17 $page.url.pathname === `${base}/` || $page.url.pathname === base || $page.url.pathname === '/'
18 );
19 +
20 + const prevNext = $derived(getPrevNext($page.url.pathname, base));
21 </script>
22
23 <ModeWatcher defaultMode="system" />
@@ -135,6 +139,9 @@
139 <article class="prose prose-gray dark:prose-invert mx-auto max-w-3xl">
140 {@render children()}
141 </article>
142 + <div class="mx-auto max-w-3xl">
143 + <PrevNextNav prev={prevNext.prev} next={prevNext.next} />
144 + </div>
145 </main>
146
147 <aside class="hidden w-56 shrink-0 border-l border-gray-200 xl:block dark:border-white/8">