nextjs-architect
Skill6 starsChanged 8 months agoMITView on GitHub
Guide Next.js App Router architecture decisions — server vs client components, layouts, routing, data fetching, middleware, and project structure. Use this skill whenever the user is building or restructuring a Next.js application, asking about App Router patterns, deciding between server and client components, setting up layouts or route groups, implementing data fetching strategies, or organizing their Next.js project structure. Also use when the user mentions page routing, dynamic routes, parallel routes, intercepting routes, or Next.js middleware.
5 other projects use a version of this.
Adopted by
Most recently taken up by
- AhmadIkbalDjaya/nextjs-architecttheir own version, first seen Nov 8, 2026
- ralvarez-ai/ralvaskillstheir own version, first seen Nov 1, 2026
- ralvarezdev/ralvaskillstheir own version, first seen Sep 29, 2026
- ghalex/skillstheir own version, first seen Jun 29, 2026
- impeto-ai/claude-skills-impetotheir own version, first seen May 18, 2026
Used with
Projects with it also use react-architect and fastapi-architect.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
ghalexSkillreact-architect
Audits a React SPA project against architecture rules. Use when asked to "review components", "check architecture", "audit this react project", "does this follow react rules", or "review my frontend structure".
Also in 3 projects that use this
ghalexSkillfastapi-architect
Audits a FastAPI project against architecture rules. Use when asked to "review routes", "check architecture", "audit this project", "does this follow fastapi rules", or "review my code structure".
Also in 3 projects that use this
Other versions5 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 5
- ghalex/skills★ 5same name
- ralvarez-ai/ralvaskills★ 4same name
- ralvarezdev/ralvaskills★ 4same name
- AhmadIkbalDjaya/nextjs-architect★ 3same name
- impeto-ai/claude-skills-impeto★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository20 other files it gives its agents.
- a11y-auditSkill
- design-an-interfaceSkill
- edit-articleSkill
- find-skillsSkill
- grill-meSkill
- improve-codebase-architectureSkill
- perf-optimizerSkill
- prd-to-issuesSkill
- react-patternsSkill
- request-refactor-planSkill
- seo-auditSkill
- solana-devSkill
- tddSkill
- triage-issueSkill
- write-a-prdSkill
- write-a-skillSkill
- docxSkill
- frontend-designSkill
- mcp-builderSkill
- skill-creatorSkill
The file
We split this skill into the rules it gives an agent
10 rules. 0 are common, 5 are distinctive, and 5 are very specific to this project.
0
Common
Most of the 4 edited versions we read keep these.
5
Distinctive
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 16
Every component is a Server Component unless it opts in with "use client".
- Distinctive: line 20
Keep sensitive logic (tokens, API keys) off the client
- Very specific: line 121
Use (groupName) to organize routes without affecting the URL.
- Very specific: line 127
Keep route-specific components, utils, and types alongside the route.
- Very specific: line 149
Use template.tsx instead of layout.tsx when you need fresh state on each navigation (e.g., enter/exit animations, per-page analytics …
- Distinctive: line 191
Use Server Actions for form submissions and mutations.
- Distinctive: line 195
When a page needs multiple independent data sources, fetch them in parallel rather than sequentially:
- Very specific: line 267
Place not-found.tsx in any route segment to customize the 404 page for that section.
- Distinctive: line 271
Use middleware for request-level logic that runs before any route is matched:
- Distinctive: line 338
Use the root layout for global metadata (site name, default OG image) and individual pages for route-specific metadata.
“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Next.js App Router Architecture
Help users make sound architectural decisions in Next.js App Router projects using Tailwind CSS for styling.
Server vs Client Components
The most important architectural decision in modern Next.js is understanding the server/client boundary.
Default to Server Components
Every component is a Server Component unless it opts in with "use client". Keep components on the server when they:
- Fetch data (database queries, API calls)
- Access backend resources directly
- Keep sensitive logic (tokens, API keys) off the client
- Render large dependencies that don't need interactivity
Use Client Components when you need
- Event handlers (onClick, onChange, onSubmit)
- State (useState, useReducer)
- Effects (useEffect, useLayoutEffect)
- Browser APIs (window, document, localStorage)
- Custom hooks that depend on state or effects
The boundary pattern
Push "use client" as far down the tree as possible. Instead of making an entire page a client component because one button needs state, extract just the interactive part:
app/
dashboard/
page.tsx ← Server Component (fetches data)
components/
stats-grid.tsx ← Server Component (renders data)
filter-bar.tsx ← Client Component (has state)Example — composing server and client:
// app/dashboard/page.tsx (Server Component)
import { FilterBar } from './components/filter-bar'
import { StatsGrid } from './components/stats-grid'
export default async function DashboardPage() {
const stats = await getStats()
return (
<div className="p-6">
<FilterBar />
<StatsGrid data={stats} />
</div>
)
}// app/dashboard/components/filter-bar.tsx
"use client"
import { useState } from 'react'
export function FilterBar() {
const [filter, setFilter] = useState('all')
return (
<div className="flex gap-2 mb-4">
{['all', 'active', 'archived'].map(f => (
<button
key={f}
onClick={() => setFilter(f)}
className={`px-3 py-1 rounded ${filter === f ? 'bg-blue-600 text-white' : 'bg-gray-100'}`}
>
{f}
</button>
))}
</div>
)
}Project Structure
Organize by feature/route rather than by type. This scales better as projects grow because related files stay together.
app/
(marketing)/ ← Route group (no URL segment)
page.tsx ← Landing page
pricing/page.tsx
about/page.tsx
layout.tsx ← Marketing-specific layout
(app)/ ← Route group for authenticated app
dashboard/
page.tsx
loading.tsx
components/ ← Route-specific components
settings/
page.tsx
layout.tsx ← App layout with sidebar/nav
api/
[...route]/route.ts ← API routes
layout.tsx ← Root layout
globals.css
components/ ← Shared components used across routes
ui/ ← Generic UI primitives (button, card, input)
forms/ ← Reusable form components
lib/
db.ts ← Database client
auth.ts ← Auth utilities
utils.ts ← General helpers
types/
index.ts ← Shared TypeScript typesRoute groups
Use (groupName) to organize routes without affecting the URL. Common patterns:
(marketing)vs(app)— different layouts for public/authenticated sections(auth)— group login/register/forgot-password under one layout
Colocation
Keep route-specific components, utils, and types alongside the route. Only promote to top-level components/ or lib/ when something is genuinely shared across multiple routes.
Layouts and Templates
Layouts persist across navigations
// app/(app)/layout.tsx
export default function AppLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-screen">
<Sidebar />
<main className="flex-1 overflow-auto">{children}</main>
</div>
)
}Layouts don't remount on navigation — their state is preserved. Use this for persistent UI like sidebars, navbars, and tab bars.
Templates remount on every navigation
Use template.tsx instead of layout.tsx when you need fresh state on each navigation (e.g., enter/exit animations, per-page analytics logging).
Data Fetching
Server Components — fetch directly
// app/products/page.tsx
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // cache for 1 hour
})
return res.json()
}
export default async function ProductsPage() {
const products = await getProducts()
return <ProductList products={products} />
}Caching strategies
{ cache: 'force-cache' }— cache indefinitely (default for GET in production){ cache: 'no-store' }— always fresh, never cached{ next: { revalidate: N } }— cache for N seconds, then revalidate in the background{ next: { tags: ['products'] } }— tag-based on-demand revalidation withrevalidateTag('products')
Server Actions for mutations
// app/products/actions.ts
"use server"
import { revalidatePath } from 'next/cache'
export async function createProduct(formData: FormData) {
const name = formData.get('name') as string
await db.product.create({ data: { name } })
revalidatePath('/products')
}Use Server Actions for form submissions and mutations. They run on the server, can access your database directly, and handle revalidation.
Parallel data fetching
When a page needs multiple independent data sources, fetch them in parallel rather than sequentially:
export default async function DashboardPage() {
const [stats, activity, notifications] = await Promise.all([
getStats(),
getRecentActivity(),
getNotifications(),
])
return (
<>
<StatsGrid data={stats} />
<ActivityFeed items={activity} />
<NotificationList notifications={notifications} />
</>
)
}Loading and Error States
loading.tsx — instant loading UI
// app/dashboard/loading.tsx
export default function DashboardLoading() {
return (
<div className="animate-pulse space-y-4 p-6">
<div className="h-8 w-48 bg-gray-200 rounded" />
<div className="grid grid-cols-3 gap-4">
{[1, 2, 3].map(i => (
<div key={i} className="h-32 bg-gray-200 rounded-lg" />
))}
</div>
</div>
)
}Next.js automatically wraps your page in a <Suspense> boundary using loading.tsx, so users see the skeleton immediately while data loads.
error.tsx — graceful error handling
// app/dashboard/error.tsx
"use client"
export default function DashboardError({
error,
reset,
}: {
error: Error
reset: () => void
}) {
return (
<div className="flex flex-col items-center justify-center p-6">
<h2 className="text-xl font-semibold mb-2">Something went wrong</h2>
<p className="text-gray-500 mb-4">{error.message}</p>
<button
onClick={reset}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
Try again
</button>
</div>
)
}not-found.tsx — custom 404 per route
Place not-found.tsx in any route segment to customize the 404 page for that section.
Middleware
Use middleware for request-level logic that runs before any route is matched:
// middleware.ts (root of project)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')
if (request.nextUrl.pathname.startsWith('/dashboard') && !token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/settings/:path*'],
}Common middleware use cases: authentication checks, redirects, A/B testing, geolocation-based routing, request logging.
Dynamic Routes
app/
products/
[id]/
page.tsx ← /products/123
[...slug]/
page.tsx ← /products/a/b/c (catch-all)
[[...slug]]/
page.tsx ← /products or /products/a/b (optional catch-all)generateStaticParams for static generation
export async function generateStaticParams() {
const products = await getProducts()
return products.map(p => ({ id: p.id }))
}This pre-renders pages at build time for known params while falling back to dynamic rendering for unknown ones.
Metadata
// Static metadata
export const metadata = {
title: 'Dashboard',
description: 'View your analytics dashboard',
}
// Dynamic metadata
export async function generateMetadata({ params }: { params: { id: string } }) {
const product = await getProduct(params.id)
return {
title: product.name,
openGraph: { images: [product.image] },
}
}Use the root layout for global metadata (site name, default OG image) and individual pages for route-specific metadata.
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.

