nextjs-route-scaffold
Skill2 starsChanged 6 months agoMITView on GitHub
Scaffolds necessary Next.js App Router file conventions and route-level SEO metadata (page.tsx, layout.tsx, error.tsx, not-found.tsx, loading.tsx, default.tsx, global-error.tsx, metadata, generateMetadata, canonical, robots, openGraph, twitter) for a given route path. Checks existing files, reads them for context, and generates only missing files. Use when user provides a route path and wants to add missing special files or SEO metadata, or asks to "scaffold", "create files for", or "set up route files" for a Next.js App Router route.
In this repository17 other files it gives its agents.
- create-nextjs-specificationSkill
- create-product-demoSkill
- find-github-repoSkill
- find-npm-packageSkill
- image-styleSkill
- perfect-promptSkill
- place-meSkill
- review-meSkill
- setup-aeo-geoSkill
- setup-harnessSkill
- songwritingSkill
- step-by-stepSkill
- stock-analysisSkill
- study-meSkill
- teach-meSkill
- three-linesSkill
- visualstormingSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
20 rules. About 0 are common, 6 are distinctive, and 14 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
14
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 11
Check if the given path exists in the project
- Distinctive: line 12
If not found, ask the user to confirm the correct path
- Very specific: line 17
Read existing files (especially page.tsx) to understand:
- Distinctive: line 19
Import patterns and UI library in use
- Very specific: line 23
If page.tsx does not exist yet, treat as PAGE_IS_ASYNC = true (new page will be async by default)
- Very specific: line 24
If page.tsx exists and its default export is async function, PAGE_IS_ASYNC = true
- Very specific: line 25
If page.tsx exists and its default export is a plain (non-async) function, PAGE_IS_ASYNC = false
- Very specific: line 28
Only relevant when PAGE_IS_ASYNC = true
- Very specific: line 29
Before creating loading.tsx, search the project for existing Skeleton or Loading components (common locations: components/ui/, …
- Very specific: line 30
If found, use them in loading.tsx
- Distinctive: line 31
If not found, create a reusable Skeleton component and use it
- Very specific: line 34
Read package.json and extract the next version (e.g.
- Distinctive: line 61
Always include metadata. Determine placement:
- Very specific: line 77
Use metadataBase in the root layout when the project needs absolute URLs.
- Very specific: line 79
Keep this skill focused on route-local SEO metadata.
- Very specific: line 105
If yes, add to page.tsx:
- Distinctive: line 115
Use content from existing files to keep new files consistent in structure and style.
- Very specific: line 161
Does NOT re-render on navigation (use template.tsx if re-render per navigation is needed)
- Very specific: line 233
Does NOT catch errors thrown by layout.tsx in the same segment
- Very specific: line 286
Must include <html> and <body> — replaces the root layout when active
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Next.js Route Scaffold
Step 1 — Analyze the route and project
1a. Resolve the path
- Check if the given path exists in the project
- If not found, ask the user to confirm the correct path
- Handle both
app/andsrc/app/structures automatically
1b. Scan existing files at the path
- List all files already present in the directory
- Read existing files (especially
page.tsx) to understand:- Component structure and naming
- Import patterns and UI library in use
- Whether components are Server or Client Components (
'use client'directive) - Styling approach (Tailwind, CSS modules, etc.)
- Whether the
Pagefunction is async or sync — store asPAGE_IS_ASYNC- If
page.tsxdoes not exist yet, treat asPAGE_IS_ASYNC = true(new page will be async by default) - If
page.tsxexists and its default export isasync function,PAGE_IS_ASYNC = true - If
page.tsxexists and its default export is a plain (non-async)function,PAGE_IS_ASYNC = false
- If
1c. Search for shared Skeleton/Loading components
- Only relevant when
PAGE_IS_ASYNC = true - Before creating
loading.tsx, search the project for existing Skeleton or Loading components (common locations:components/ui/,components/common/,components/shared/) - If found, use them in
loading.tsx - If not found, create a reusable Skeleton component and use it
1d. Check Next.js version
- Read
package.jsonand extract thenextversion (e.g."next": "^16.2.0") - Strip semver prefix (
^,~) and compare major.minor: ≥ 16.2 → useunstable_retry; < 16.2 → usereset - Store this as
USE_UNSTABLE_RETRYfor use in Step 5
1e. Parse route characteristics
- Is root layout? (
app/layout.tsxorsrc/app/layout.tsx) — needs<html>and<body>tags +global-error.tsx - Has dynamic segments? (
[slug],[...slug],[[...slug]]) — affectsparamstype, prompt forgenerateStaticParams - Is parallel route slot? (segment starts with
@) — may needdefault.tsx - Is route group? (
(group)) — ignore for naming; URL-invisible
Step 2 — Decide which files to create
Skip files that already exist. Create only what is missing.
| File | Create when |
page.tsx | Always — makes route publicly accessible |
layout.tsx | Route needs shared UI wrapping children; always for root app/ |
loading.tsx | Only when PAGE_IS_ASYNC = true — use shared Skeleton or create one; if PAGE_IS_ASYNC = false and loading.tsx exists, delete it |
error.tsx | Always |
not-found.tsx | Route has dynamic segments ([id], [slug], etc.) |
global-error.tsx | Root app/ only |
default.tsx | Parallel route slots (@slotName) only — explain and confirm first |
template.tsx | Only when user explicitly requests — explain and confirm first |
Step 3 — Metadata and SEO rules
Always include metadata. Determine placement:
page.tsxdoes not exist → add to newpage.tsxpage.tsxexists and is a Server Component → add topage.tsxpage.tsxexists and is a Client Component → add tolayout.tsx- Both
page.tsxandlayout.tsxare Client Components → stop and ask the user
For public indexable pages, include the minimum SEO set that fits the route:
titledescriptionalternates.canonicalopenGraphtwitterrobots
Use metadataBase in the root layout when the project needs absolute URLs. For routes that should not be indexed, set robots.index = false and robots.follow = false.
Keep this skill focused on route-local SEO metadata. If the user wants site-wide SEO assets such as sitemap.ts, robots.ts, or structured data conventions, use references/seo.md for the detailed patterns.
Dynamic routes use generateMetadata; static routes use metadata constant:
// Static route
export const metadata: Metadata = {
title: 'Page Title',
}
// Dynamic route
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>
}): Promise<Metadata> {
const { slug } = await params
return { title: slug }
}Step 4 — Dynamic routes: ask about generateStaticParams
For routes with dynamic segments ([id], [slug], etc.), ask:
"Do you want this route statically generated (SSG)? I can add
generateStaticParamsif so."
If yes, add to page.tsx:
export async function generateStaticParams() {
return [{ slug: 'example' }]
}Step 5 — Generate each file
Use content from existing files to keep new files consistent in structure and style.
page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
return <div>{slug}</div>
}- Server Component by default
paramsandsearchParamsarePromise<...>— alwaysawait- Omit
paramsif no dynamic segments; omitsearchParamsif not needed
layout.tsx
Non-root:
export default async function Layout({
children,
params,
}: {
children: React.ReactNode
params: Promise<{ slug: string }>
}) {
return <section>{children}</section>
}Root (app/layout.tsx):
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}- Cannot read
searchParams - Does NOT re-render on navigation (use
template.tsxif re-render per navigation is needed)
loading.tsx
// Use shared Skeleton component if available; otherwise create one
export default function Loading() {
return <SkeletonComponent />
}- Server Component, no props
- Automatically wraps
page.tsxin a<Suspense>boundary
error.tsx
Next.js ≥ 16.2 (use unstable_retry — re-fetches + re-renders):
'use client'
import { useEffect } from 'react'
export default function Error({
error,
unstable_retry,
}: {
error: Error & { digest?: string }
unstable_retry: () => void
}) {
useEffect(() => {
console.error(error)
}, [error])
return (
<div>
<h2>Something went wrong</h2>
<button onClick={unstable_retry}>Try again</button>
</div>
)
}Next.js < 16.2 (use reset — clears error state only, no re-fetch):
'use client'
import { useEffect } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
console.error(error)
}, [error])
return (
<div>
<h2>Something went wrong</h2>
<button onClick={reset}>Try again</button>
</div>
)
}- Must be
'use client' error.tsxrenders inside the parentlayout.tsx— surrounding layout stays intact. Keep error UI self-contained (no full-page styles).- Does NOT catch errors thrown by
layout.tsxin the same segment - For root layout errors, use
global-error.tsxinstead
global-error.tsx (root only)
Same version rule applies (USE_UNSTABLE_RETRY from Step 1d).
Next.js ≥ 16.2:
'use client'
export default function GlobalError({
error,
unstable_retry,
}: {
error: Error & { digest?: string }
unstable_retry: () => void
}) {
return (
<html>
<body>
<h2>Something went wrong</h2>
<button onClick={unstable_retry}>Try again</button>
</body>
</html>
)
}Next.js < 16.2:
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body>
<h2>Something went wrong</h2>
<button onClick={reset}>Try again</button>
</body>
</html>
)
}- Must include
<html>and<body>— replaces the root layout when active
not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
<div>
<h2>Not Found</h2>
<p>Could not find the requested resource.</p>
<Link href="/">Return Home</Link>
</div>
)
}- Triggered by calling
notFound()fromnext/navigation - Server Component
default.tsx (parallel routes only)
Explain before creating:
"
default.tsxis used in Parallel Routes (@slotName) as a fallback when Next.js cannot recover the slot's active state after a full-page load. Do you need this?"
export default function Default() {
return null
}template.tsx (on request only)
Explain before creating:
"
template.tsxworks likelayout.tsxbut re-mounts on every navigation. Useful for per-visit animations,useEffectpage-view tracking, or resetting state on tab switches. Do you need this?"
Step 6 — Component hierarchy reference
layout.tsx
└── error.tsx ← error boundary
└── loading.tsx ← Suspense boundary
└── not-found.tsx
└── page.tsxerror.tsx does NOT wrap layout.tsx of the same segment.
Dynamic segment type reference
| Pattern | params type |
[slug] | { slug: string } |
[id] | { id: string } |
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

