Hierarchical Pages Feature Spec
By Claude Sonnet 4.5, Jay Griffin · January 21, 2026
docsProject: jaygriff
🏷️ Tags:feature-specroutingcontent-system
Technical specification for parent-child page relationships with automatic routing and dev-mode creation.
Problem Statement
Large documents (like the roadmap) become walls of text. Need to break them into linked sub-pages without:
- Manually creating routes
- Copy-pasting sorting/filtering logic
- Managing nested URL structures
- Breaking links when re-organizing
Solution: Notion-Style Flat Routing
Parent-child relationships exist in metadata only. URLs remain flat. A child page under "Roadmap" lives at/docs/search-feature-spec, not /docs/roadmap/search-feature-spec. The hierarchy is shown through breadcrumbs and automatic child listings.
Data Model
PostMeta Extension
export interface PostMeta {
title: string;
slug: string;
date: string;
author: string[];
type: 'post' | 'doc' | 'commit';
projectId: string;
description?: string;
tags?: string[];
authorshipNote?: string;
commitHash?: string;
parentSlug?: string; // NEW: Optional parent page slug
}Example Usage
// Child page metadata
export const metadata: PostMeta = {
title: 'Search Feature Deep Dive',
slug: 'search-feature-spec',
parentSlug: 'roadmap', // References parent
date: '2026-01-21T00:00:00Z',
type: 'doc',
// ... other fields
};Core Features
1. Parent Page Auto-Listing
Parent pages automatically show their children. Create a reusable component:
export async function ChildPages({ parentSlug }: { parentSlug: string }) {
const allPages = await getAllDocs();
const children = allPages
.filter(page => page.metadata.parentSlug === parentSlug)
.sort((a, b) => new Date(b.metadata.date).getTime() - new Date(a.metadata.date).getTime());
if (children.length === 0) return null;
return (
<>
<Heading level={3}>Pages in this section</Heading>
<List>
{children.map(page => (
<ListItem key={page.slug}>
<Link href={`/docs/${page.slug}`}>
{page.metadata.title}
</Link>
{' - '}
{formatDate(page.metadata.date)}
</ListItem>
))}
</List>
</>
);
}Usage in parent page:
const RoadmapDoc = () => {
return (
<>
<Heading level={2}>Roadmap Overview</Heading>
<Paragraph>Main roadmap content...</Paragraph>
<ChildPages parentSlug="roadmap" />
</>
);
};2. Breadcrumb Navigation
Child pages show breadcrumb trail. Add to ContentHeader component:
export async function Breadcrumbs({ currentPage }: { currentPage: PostMeta }) {
if (!currentPage.parentSlug) return null;
const parent = await getDocBySlug(currentPage.parentSlug);
if (!parent) return null;
return (
<nav>
<Link href={`/docs/${parent.metadata.slug}`}>
{parent.metadata.title}
</Link>
{' / '}
<span>{currentPage.title}</span>
</nav>
);
}3. Dev Mode Page Creation
Click "New Child Page" button in dev mode on any page. Modal workflow:
- User enters page title
- Generate slug from title (kebab-case)
- Create new .tsx file in content/tsx/
- Set parentSlug to current page's slug
- Auto-populate metadata
- Navigate to new page or refresh
export async function POST(request: Request) {
if (process.env.NODE_ENV !== 'development') {
return Response.json({ error: 'Not available in production' }, { status: 403 });
}
const { title, parentSlug } = await request.json();
const slug = kebabCase(title);
const timestamp = new Date().toISOString();
const fileContent = \`
import { Heading, Paragraph } from '@/components/Primitives';
import type { PostMeta } from '@/types/post';
export const metadata: PostMeta = {
title: '\${title}',
slug: '\${slug}',
parentSlug: '\${parentSlug}',
date: '\${timestamp}',
author: ['Jay Griffin'],
type: 'doc',
projectId: 'jaygriff',
description: '',
tags: [],
};
const \${pascalCase(slug)} = () => {
return (
<>
<Heading level={2}>Overview</Heading>
<Paragraph>Start writing...</Paragraph>
</>
);
};
export default \${pascalCase(slug)};
\`;
await fs.writeFile(
path.join(process.cwd(), 'content/tsx', \`\${slug}.tsx\`),
fileContent
);
return Response.json({ success: true, slug });
}Implementation Phases
Phase 1: Data Model (No UI Changes)
- Add parentSlug to PostMeta type
- Add parentSlug to this spec doc as test
- Verify existing pages still work
Phase 2: Reading Features
- Create ChildPages component
- Create Breadcrumbs component
- Add to ContentHeader
- Test by manually adding parentSlug to a few docs
Phase 3: Writing Features (Dev Mode)
- Create /api/create-page endpoint
- Add "New Child Page" button to ContentHeader (dev only)
- Build page creation modal
- Test creating child pages
Phase 4: Enhanced Features
- Page move/re-parent capability
- Bulk operations (move all children)
- Page deletion (dev mode)
- Sibling navigation (next/prev)
Edge Cases
- Orphaned pages: Parent deleted but children still reference it. Show warning in dev mode.
- Circular references: Page A → Page B → Page A. Detect and prevent.
- Deep nesting: No limit, but breadcrumbs might get long. Consider showing only last 2-3 levels.
- Slug collisions: Check if slug exists before creating. Auto-append number if needed.
Open Questions
- Should Navigator component show hierarchy? (indented child pages under parents)
- Max nesting depth limit? (Notion allows infinite, but UX suffers)
- Sort order for children: by date, manual order, alphabetical?
- Allow pages to have multiple parents? (tags vs hierarchy)
Success Metrics
- Zero manual route files needed for new pages
- Can create and link child page in under 10 seconds
- Roadmap broken into 5+ child specs
- All hierarchy visible through breadcrumbs + child listings