worldcrafter-route-creator

hopeoverture's avatarfrom hopeoverture

Scaffold Next.js App Router routes for simple pages without forms or complex logic. Use when user needs "create a page", "add route", "add about/contact/terms page", "create API endpoint", "add layout", or mentions SSE/webhooks. Generates page, layout, loading, error, and not-found files with boilerplate. Best for static content, read-only pages, and API endpoints (REST, SSE, webhooks). Do NOT use for forms with validation (use worldcrafter-feature-builder), complete features with database (use worldcrafter-feature-builder), database-only changes (use worldcrafter-database-setup), or comprehensive testing (use worldcrafter-test-generator).

⭐0stars🔀0forks📁View on GitHub🕐Updated Nov 10, 2025

When & Why to Use This Skill

The WorldCrafter Route Creator is a specialized developer tool designed to automate the scaffolding of Next.js App Router structures. It streamlines the creation of pages, layouts, loading states, and error boundaries, as well as specialized API endpoints like Server-Sent Events (SSE) and webhooks. By generating standardized boilerplate code and following Next.js 15+ best practices, it ensures consistent project architecture and significantly reduces manual setup time for developers.

Use Cases

  • Rapidly scaffolding static marketing or informational pages such as 'About Us', 'Privacy Policy', or 'Contact' with integrated loading and error UI.
  • Setting up complex API Route Handlers for real-time updates via SSE, secure file uploads to Supabase, or Stripe webhook signature verification.
  • Creating dynamic route segments (e.g., /worlds/[slug] or /posts/[id]) with pre-configured metadata generation and server-side data fetching patterns.
  • Organizing application architecture using Route Groups to separate public marketing content from protected dashboard areas without affecting the URL structure.
  • Implementing nested layouts for shared UI components across specific route segments, ensuring state preservation and consistent navigation.
nameworldcrafter-route-creator
descriptionScaffold Next.js App Router routes for simple pages without forms or complex logic. Use when user needs "create a page", "add route", "add about/contact/terms page", "create API endpoint", "add layout", or mentions SSE/webhooks. Generates page, layout, loading, error, and not-found files with boilerplate. Best for static content, read-only pages, and API endpoints (REST, SSE, webhooks). Do NOT use for forms with validation (use worldcrafter-feature-builder), complete features with database (use worldcrafter-feature-builder), database-only changes (use worldcrafter-database-setup), or comprehensive testing (use worldcrafter-test-generator).

WorldCrafter Route Creator

Version: 2.0.0 Last Updated: 2025-01-15

This skill provides tools and templates for scaffolding Next.js App Router routes with all necessary files and boilerplate code.

Skill Metadata

Related Skills:

  • worldcrafter-feature-builder - Use instead for complete features with forms and validation
  • worldcrafter-auth-guard - Use to protect routes after creation
  • worldcrafter-test-generator - Use to add tests for created routes

Example Use Cases:

  • "Create an about us page" → Generates page.tsx with static content, loading.tsx, and error.tsx at /about
  • "Add an API endpoint for users" → Creates route.ts with GET/POST handlers at /api/users
  • "Create a dashboard layout" → Generates layout.tsx with navigation and shared UI for /dashboard routes
  • "Add a dynamic product page" → Creates /products/[id]/page.tsx with dynamic route handling

When to Use This Skill

Use this skill when:

  • Creating new pages/routes in the application
  • Setting up route groups or nested routes
  • Creating API routes (Route Handlers)
  • Adding layouts for route segments
  • Setting up protected/authenticated routes
  • Creating dynamic routes with parameters
  • Adding loading states and error boundaries

Next.js App Router Overview

WorldCrafter uses Next.js 15+ App Router with these file conventions:

  • page.tsx - UI for a route (makes route publicly accessible)
  • layout.tsx - Shared UI for segment and children
  • loading.tsx - Loading UI (Suspense boundary)
  • error.tsx - Error UI (Error boundary)
  • not-found.tsx - 404 UI for not found errors
  • route.ts - API endpoint (Route Handler)

Route Scaffolding Process

Phase 1: Plan Route Structure

Consider:

  • Route path (e.g., /worlds/[slug], /api/upload, /api/sse/activity)
  • Is it a page or API route?
  • Does it need authentication?
  • Does it need a custom layout?
  • What loading/error states are needed?

WorldCrafter Route Reference: See references/worldcrafter-routes.md for complete application route tree with all patterns:

  • World management routes (/worlds/[slug]/*)
  • Entity routes (characters, locations, events, items, factions)
  • Visualization routes (graph, map, timeline, wiki)
  • API routes (upload, export, MCP, SSE, webhooks)
  • Auth routes (login, signup, reset-password)
  • Marketing routes (landing, about, pricing)

Phase 2: Scaffold Route

Automated Scaffolding:

python .claude/skills/worldcrafter-route-creator/scripts/scaffold_route.py <route-path>

Examples:

# Basic page route
python scaffold_route.py dashboard

# Nested route
python scaffold_route.py dashboard/settings

# Dynamic route
python scaffold_route.py posts/[id]

# Route group
python scaffold_route.py (marketing)/about

# API route
python scaffold_route.py api/users --api

This creates:

  • src/app/<route-path>/page.tsx (or route.ts for API)
  • src/app/<route-path>/loading.tsx
  • src/app/<route-path>/error.tsx
  • Optional: layout.tsx, not-found.tsx

Manual Approach: Copy templates from assets/templates/ and customize.

Phase 3: Customize Route Files

Page Component (page.tsx)

export default function DashboardPage() {
  return (
    <div className="container mx-auto py-8">
      <h1 className="text-3xl font-bold">Dashboard</h1>
      {/* Page content */}
    </div>
  )
}

Server Component Features:

  • Async data fetching
  • Direct database access
  • No client-side JavaScript by default

Client Component Features:

  • Add "use client" directive
  • Interactive elements
  • Hooks (useState, useEffect, etc.)

Layout (layout.tsx)

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="dashboard-layout">
      <nav>{/* Navigation */}</nav>
      <main>{children}</main>
    </div>
  )
}

Layout Features:

  • Shared UI across routes
  • Preserved state on navigation
  • Can be nested
  • Cannot access route parameters (use page)

Loading State (loading.tsx)

export default function Loading() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <div className="h-12 w-12 animate-spin rounded-full border-4 border-muted border-t-primary" />
    </div>
  )
}

Features:

  • Instant loading state
  • Wrapped in Suspense automatically
  • Streamed from server

Error Boundary (error.tsx)

'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div className="flex min-h-screen flex-col items-center justify-center">
      <h2 className="text-2xl font-bold">Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
  )
}

Features:

  • Must be Client Component
  • Catches errors in children
  • Provides reset function
  • Can be nested

API Route (route.ts)

import { NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'

export async function GET() {
  const data = await prisma.model.findMany()
  return NextResponse.json(data)
}

export async function POST(request: Request) {
  const body = await request.json()
  const result = await prisma.model.create({ data: body })
  return NextResponse.json(result)
}

Supported Methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS

Phase 4: Add Metadata

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Dashboard',
  description: 'User dashboard page'
}

export default function DashboardPage() {
  // ...
}

Dynamic Metadata:

export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.id)
  return {
    title: post.title,
    description: post.excerpt
  }
}

WorldCrafter API Route Patterns

WorldCrafter uses several specialized API route patterns. See references/worldcrafter-routes.md for complete implementations.

File Upload to Supabase Storage

// src/app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@/lib/supabase/server'

export async function POST(request: NextRequest) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()

  if (!user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const formData = await request.formData()
  const file = formData.get('file') as File

  const { data, error } = await supabase.storage
    .from('uploads')
    .upload(`${user.id}/${file.name}`, file)

  if (error) {
    return NextResponse.json({ error: error.message }, { status: 500 })
  }

  return NextResponse.json({ url: data.path })
}

Server-Sent Events (SSE) for Real-Time Updates

// src/app/api/sse/activity/route.ts
import { NextRequest } from 'next/server'
import { createClient } from '@/lib/supabase/server'

export async function GET(request: NextRequest) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()

  if (!user) {
    return new Response('Unauthorized', { status: 401 })
  }

  const stream = new ReadableStream({
    async start(controller) {
      const encoder = new TextEncoder()

      controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'connected' })}\n\n`))

      const channel = supabase
        .channel('activity')
        .on('postgres_changes', {
          event: '*',
          schema: 'public',
          table: 'activities',
          filter: `user_id=eq.${user.id}`
        }, (payload) => {
          controller.enqueue(encoder.encode(`data: ${JSON.stringify(payload)}\n\n`))
        })
        .subscribe()

      request.signal.addEventListener('abort', () => {
        channel.unsubscribe()
        controller.close()
      })
    }
  })

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive'
    }
  })
}

MCP Server (JSON-RPC 2.0)

// src/app/api/mcp/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@/lib/supabase/server'
import { prisma } from '@/lib/prisma'

interface JsonRpcRequest {
  jsonrpc: '2.0'
  method: string
  params?: unknown
  id: string | number
}

export async function POST(request: NextRequest) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()

  if (!user) {
    return NextResponse.json({
      jsonrpc: '2.0',
      error: { code: -32001, message: 'Unauthorized' },
      id: null
    }, { status: 401 })
  }

  const rpcRequest: JsonRpcRequest = await request.json()

  try {
    const result = await handleMcpMethod(rpcRequest.method, rpcRequest.params, user.id)
    return NextResponse.json({
      jsonrpc: '2.0',
      result,
      id: rpcRequest.id
    })
  } catch (error) {
    return NextResponse.json({
      jsonrpc: '2.0',
      error: {
        code: -32603,
        message: error instanceof Error ? error.message : 'Internal error'
      },
      id: rpcRequest.id
    }, { status: 500 })
  }
}

Webhook Handler (with Signature Verification)

// src/app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { headers } from 'next/headers'
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-11-20.acacia'
})

export async function POST(request: NextRequest) {
  const body = await request.text()
  const signature = (await headers()).get('stripe-signature')!

  let event: Stripe.Event

  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    )
  } catch (err) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })
  }

  // Handle event types
  switch (event.type) {
    case 'checkout.session.completed':
      // Handle successful checkout
      break
    case 'customer.subscription.deleted':
      // Handle subscription cancellation
      break
  }

  return NextResponse.json({ received: true })
}

Background Job (Export)

// src/app/api/export/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@/lib/supabase/server'
import { prisma } from '@/lib/prisma'

export async function POST(request: NextRequest) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()

  if (!user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const { worldId } = await request.json()

  // Create export job
  const job = await prisma.exportJob.create({
    data: {
      userId: user.id,
      worldId,
      status: 'pending'
    }
  })

  // Trigger background processing
  // await queueExportJob(job.id)

  return NextResponse.json({ jobId: job.id })
}

Common Route Patterns

Basic Page Route

src/app/
└── about/
    ├── page.tsx       # /about
    ├── loading.tsx
    └── error.tsx

Nested Routes

src/app/
└── dashboard/
    ├── page.tsx           # /dashboard
    ├── layout.tsx
    ├── settings/
    │   └── page.tsx       # /dashboard/settings
    └── profile/
        └── page.tsx       # /dashboard/profile

Dynamic Routes

src/app/
└── posts/
    ├── page.tsx           # /posts
    └── [id]/
        ├── page.tsx       # /posts/123
        ├── edit/
        │   └── page.tsx   # /posts/123/edit
        └── not-found.tsx

Access Parameters:

export default function PostPage({ params }: { params: { id: string } }) {
  return <div>Post ID: {params.id}</div>
}

WorldCrafter World Management Routes

src/app/
└── worlds/
    ├── page.tsx                        # /worlds - List all user's worlds
    ├── new/
    │   └── page.tsx                    # /worlds/new - Create world
    └── [slug]/
        ├── page.tsx                    # /worlds/:slug - Dashboard
        ├── settings/
        │   └── page.tsx                # /worlds/:slug/settings
        ├── characters/
        │   ├── page.tsx                # /worlds/:slug/characters - List
        │   ├── new/
        │   │   └── page.tsx            # /worlds/:slug/characters/new
        │   └── [id]/
        │       ├── page.tsx            # /worlds/:slug/characters/:id - Detail
        │       └── edit/
        │           └── page.tsx        # /worlds/:slug/characters/:id/edit
        ├── graph/
        │   └── page.tsx                # /worlds/:slug/graph - Visualization
        ├── map/
        │   └── page.tsx                # /worlds/:slug/map - Interactive map
        └── timeline/
            └── page.tsx                # /worlds/:slug/timeline - Events timeline

Pattern Features:

  • World slug for human-readable URLs
  • Nested entity routes (characters, locations, events, items, factions)
  • Consistent CRUD pattern (list, new, [id], [id]/edit)
  • Visualization routes (graph, map, timeline)
  • All routes protected (auth required)
  • World ownership verified in layout

Catch-All Routes

src/app/
└── docs/
    └── [...slug]/
        └── page.tsx       # /docs/a, /docs/a/b, /docs/a/b/c

Optional Catch-All:

└── [[...slug]]/           # Matches /docs too

WorldCrafter Wiki Example:

src/app/
└── worlds/
    └── [slug]/
        └── wiki/
            ├── page.tsx                # /worlds/:slug/wiki - Home
            └── [pageSlug]/
                └── page.tsx            # /worlds/:slug/wiki/:pageSlug

Route Groups

src/app/
├── (auth)/                # Auth route group (not in URL)
│   ├── layout.tsx         # Auth-specific layout
│   ├── login/
│   │   └── page.tsx       # /login
│   ├── signup/
│   │   └── page.tsx       # /signup
│   └── reset-password/
│       └── page.tsx       # /reset-password
└── (marketing)/           # Marketing route group (not in URL)
    ├── layout.tsx         # Marketing layout
    ├── page.tsx           # / (landing)
    ├── about/
    │   └── page.tsx       # /about
    └── pricing/
        └── page.tsx       # /pricing

WorldCrafter Use Cases:

  • Different layouts for auth vs marketing pages
  • Organize routes logically without affecting URLs
  • Share navigation and styling within groups
  • Separate public and authenticated sections

Parallel Routes

src/app/
└── dashboard/
    ├── @analytics/
    │   └── page.tsx
    ├── @team/
    │   └── page.tsx
    └── page.tsx

Layout:

export default function Layout({
  children,
  analytics,
  team,
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  return (
    <>
      {children}
      {analytics}
      {team}
    </>
  )
}

Intercepting Routes

src/app/
├── feed/
│   └── page.tsx
├── photo/
│   └── [id]/
│       └── page.tsx       # /photo/123
└── @modal/
    └── (.)photo/
        └── [id]/
            └── page.tsx   # Intercepts /photo/123

API Routes

src/app/
└── api/
    ├── upload/
    │   └── route.ts           # POST /api/upload - File upload
    ├── export/
    │   └── route.ts           # POST /api/export - Background job
    ├── mcp/
    │   └── route.ts           # POST /api/mcp - MCP server (JSON-RPC)
    ├── sse/
    │   └── activity/
    │       └── route.ts       # GET /api/sse/activity - Server-Sent Events
    └── webhooks/
        └── stripe/
            └── route.ts       # POST /api/webhooks/stripe - Stripe webhook

WorldCrafter API Patterns:

  • File upload to Supabase Storage
  • Background job creation (export)
  • MCP server with JSON-RPC 2.0
  • Server-Sent Events for real-time updates
  • Webhook handlers with signature verification

Authentication Patterns

Protected Page Route

import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'

export default async function ProtectedPage() {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()

  if (!user) {
    redirect('/login')
  }

  return <div>Protected content for {user.email}</div>
}

Protected API Route

import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'

export async function GET() {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()

  if (!user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  // Fetch user-specific data
  const data = await prisma.data.findMany({
    where: { userId: user.id }
  })

  return NextResponse.json(data)
}

Protected Layout

import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'

export default async function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()

  if (!user) {
    redirect('/login')
  }

  return (
    <div className="dashboard-layout">
      {children}
    </div>
  )
}

Data Fetching Patterns

import { prisma } from '@/lib/prisma'

export default async function PostsPage() {
  const posts = await prisma.post.findMany()

  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>{post.title}</article>
      ))}
    </div>
  )
}

Parallel Data Fetching

async function getData() {
  const [users, posts] = await Promise.all([
    prisma.user.findMany(),
    prisma.post.findMany()
  ])

  return { users, posts }
}

export default async function Page() {
  const { users, posts } = await getData()
  // ...
}

Sequential Data Fetching

export default async function Page({ params }: { params: { id: string } }) {
  const user = await prisma.user.findUnique({
    where: { id: params.id }
  })

  // Waits for user before fetching posts
  const posts = await prisma.post.findMany({
    where: { authorId: user.id }
  })

  return <div>{/* ... */}</div>
}

Reference Files

  • references/worldcrafter-routes.md - Complete WorldCrafter route tree with all API patterns
  • references/related-skills.md - How this skill works with other WorldCrafter skills
  • assets/templates/ - All route file templates

Key patterns in worldcrafter-routes.md:

  • World management structure (/worlds/[slug]/*)
  • Entity CRUD patterns (characters, locations, events, items, factions)
  • API routes (file upload, background jobs, MCP server, SSE, webhooks)
  • Auth route group patterns
  • Nested layouts and route groups
  • Dynamic route parameter handling

Skill Orchestration

This skill is best for creating simple pages and routes. For complete features with forms, use worldcrafter-feature-builder instead.

Common Workflows

Simple Static Pages:

  1. worldcrafter-route-creator (this skill) - Create page structure
  2. Add content and styling
  3. Optionally use worldcrafter-test-generator to add tests

API Endpoints:

  1. worldcrafter-route-creator (this skill) - Create route.ts with handlers
  2. Add business logic and database operations
  3. Optionally use worldcrafter-auth-guard to protect endpoints

Protected Pages:

  1. worldcrafter-route-creator (this skill) - Create page structure
  2. worldcrafter-auth-guard - Add authentication checks

Dynamic Routes:

  1. worldcrafter-route-creator (this skill) - Create [id]/page.tsx structure
  2. Add data fetching and rendering logic
  3. Add not-found.tsx for invalid IDs

When Claude Should Use Multiple Skills

Claude will orchestrate route-creator with other skills when:

  • User wants a simple page that later needs protection (route-creator → auth-guard)
  • User wants to create API endpoints (route-creator → auth-guard for protection)
  • User wants static pages with comprehensive tests (route-creator → test-generator)

Example orchestration:

User: "Create an about page and a contact page"

Claude's workflow:
1. worldcrafter-route-creator (this skill):
   - Create /about page with loading and error states
   - Create /contact page with loading and error states

Alternative - Complete Feature:

User: "Create a contact form"

Claude's workflow (DO NOT use route-creator):
1. worldcrafter-feature-builder (use instead):
   - Creates /contact route with form
   - Adds validation schema
   - Creates Server Action
   - Includes tests

Skill Selection Guidance

Choose this skill when:

  • User wants "simple page", "about page", "static content"
  • User mentions "API endpoint" or "route handler"
  • User wants layouts or route structure only
  • No forms or validation needed

Choose worldcrafter-feature-builder instead when:

  • User mentions "form", "submit", "validation", "feature"
  • User wants complete functionality with database
  • User wants CRUD operations
  • User wants comprehensive implementation

Use this skill for:

  • Static content pages (about, terms, privacy)
  • API endpoints (Route Handlers)
  • Layouts and route groups
  • Simple read-only pages
  • Dynamic routes without forms

Do NOT use this skill for:

  • Forms with validation (use feature-builder)
  • Complete features (use feature-builder)
  • Database operations (use database-setup or feature-builder)

Route Scaffolding Script Reference

# Basic route
python scaffold_route.py dashboard

# With layout
python scaffold_route.py dashboard --with-layout

# API route
python scaffold_route.py api/users --api

# Dynamic route
python scaffold_route.py posts/[id]

# Protected route
python scaffold_route.py dashboard --protected

Troubleshooting

Route Not Found

  • Ensure page.tsx exists in route folder
  • Check file naming (lowercase, no typos)
  • Verify folder structure matches URL

Layout Not Applied

  • Layout must be named layout.tsx
  • Layout applies to children only
  • Check layout is in correct folder

Loading State Not Showing

  • Add loading.tsx in route folder
  • Ensure page has async operations
  • Check Suspense boundaries

Error Boundary Not Catching

  • Error boundary must be Client Component ('use client')
  • Only catches errors in children
  • Add error.tsx at appropriate level

Best Practices

  1. Use Server Components by default - Add 'use client' only when needed
  2. Colocate files - Keep components/utilities with routes
  3. Use layouts for shared UI - Navigation, headers, footers
  4. Add loading states - Improve perceived performance
  5. Handle errors gracefully - Provide reset functionality
  6. Protect sensitive routes - Check auth in server components
  7. Optimize metadata - Use generateMetadata for dynamic content
  8. Use route groups - Organize routes without affecting URLs

Success Criteria

A complete route setup includes:

  • ✅ Page component created
  • ✅ Loading state added
  • ✅ Error boundary added
  • ✅ Layout (if shared UI needed)
  • ✅ Metadata configured
  • ✅ Authentication check (if protected)
  • ✅ Not-found page (for dynamic routes)
  • ✅ Route accessible at correct URL