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:

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

src/types/post.ts
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

.ts
// 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:

src/components/ChildPages.tsx
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:

content/tsx/roadmap.tsx
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:

src/components/Breadcrumbs.tsx
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:

src/app/api/create-page/route.ts
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)

Phase 2: Reading Features

Phase 3: Writing Features (Dev Mode)

Phase 4: Enhanced Features

Edge Cases

Open Questions

Success Metrics