Claude Code Plugins

Community-maintained marketplace

Feedback

worldcrafter-route-creator

@hopeoverture/worldcrafter-v2
0
0

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).

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name worldcrafter-route-creator
description 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).

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

Server Component (Recommended)

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