Claude-skill-registry add-protected-page
Creates a protected page with Suspense loading pattern. Use when adding new pages that require authentication, creating dashboard pages, or setting up new routes.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/add-protected-page" ~/.claude/skills/majiayu000-claude-skill-registry-add-protected-page && rm -rf "$T"
manifest:
skills/data/add-protected-page/SKILL.mdsource content
Add Protected Page
Creates a protected page following the Suspense loading pattern with Clerk authentication.
Structure
For a new route
/my-feature:
front/ ├── app/my-feature/ │ ├── page.tsx # Auth only - wraps content in Suspense │ └── loading.tsx # Automatic Suspense fallback └── components/my-feature/ └── MyFeatureContent.tsx # Data fetching + UI
Workflow
1. Create Page Component
// app/my-feature/page.tsx 'use client'; import { Suspense } from 'react'; import { useUser } from '@clerk/nextjs'; import { MyFeatureContent } from '@/components/my-feature/MyFeatureContent'; export default function MyFeaturePage() { const { user } = useUser(); if (!user) return null; // REQUIRED for SSG return ( <Suspense> <MyFeatureContent userId={user.id} /> </Suspense> ); }
Key Points:
directive required'use client'
is MANDATORY - prevents build errorsif (!user) return null- Page only handles auth, wraps content in Suspense
- No data fetching here
2. Create Loading Fallback
// app/my-feature/loading.tsx import { LoadingSpinner } from '@/components/_shared/loading-spinner'; export default function Loading() { return <LoadingSpinner message="Loading..." />; }
3. Create Content Component
// components/my-feature/MyFeatureContent.tsx 'use client'; import { useMyFeatureSuspense } from '@/lib/hooks/use-my-feature'; interface MyFeatureContentProps { userId: string; } export function MyFeatureContent({ userId }: MyFeatureContentProps) { const { data } = useMyFeatureSuspense(userId); return ( <div className="container mx-auto p-4"> {/* Render data - no isLoading check needed! */} {data.map((item) => ( <div key={item.id}>{item.name}</div> ))} </div> ); }
Key Points:
- Uses
- no loading states neededuseSuspenseQuery - Receives
as prop from pageuserId - Pure UI rendering
4. Update Middleware (if new route pattern)
// middleware.ts const isProtectedRoute = createRouteMatcher([ '/dashboard(.*)', '/items(.*)', '/paid-feature(.*)', '/my-feature(.*)', // Add new route ]);
Important Rules
DO:
- Add
in page componentsif (!user) return null - Use Suspense to wrap content
- Put data fetching in content component
- Use
for automatic loading statesuseSuspenseQuery
DO NOT:
- Fetch data in page component
- Add manual loading states (
)isLoading - Forget the null check (causes build errors)
- Use
(middleware handles auth)dynamic = 'force-dynamic'
Creating the Feature Hook
See the
add-feature-hook skill for creating the hook used in the content component.