$npx -y skills add sabahattink/antigravity-fullstack-hq --skill nextjs-app-routerNext.js App Router best practices, Server Components, Server Actions, routing patterns, and data fetching strategies. Use when building Next.js applications with the App Router.
| 1 | # Next.js App Router Patterns |
| 2 | |
| 3 | ## Project Structure |
| 4 | |
| 5 | ``` |
| 6 | app/ |
| 7 | ├── (auth)/ # Route Group |
| 8 | │ ├── login/page.tsx |
| 9 | │ ├── register/page.tsx |
| 10 | │ └── layout.tsx |
| 11 | ├── (dashboard)/ |
| 12 | │ ├── layout.tsx |
| 13 | │ ├── page.tsx |
| 14 | │ └── [projectId]/ |
| 15 | │ └── page.tsx |
| 16 | ├── api/ |
| 17 | │ └── webhooks/route.ts |
| 18 | ├── layout.tsx |
| 19 | ├── page.tsx |
| 20 | ├── loading.tsx |
| 21 | ├── error.tsx |
| 22 | └── not-found.tsx |
| 23 | ``` |
| 24 | |
| 25 | ## Server vs Client Components |
| 26 | |
| 27 | ### Decision Tree |
| 28 | |
| 29 | - Need interactivity (onClick, useState)? -> 'use client' |
| 30 | - Need browser APIs? -> 'use client' |
| 31 | - Otherwise -> Server Component (default) |
| 32 | |
| 33 | ### Server Component |
| 34 | |
| 35 | ```tsx |
| 36 | // No directive needed - Server Component by default |
| 37 | import { prisma } from '@/lib/db' |
| 38 | |
| 39 | export default async function UsersPage() { |
| 40 | const users = await prisma.user.findMany() |
| 41 | return <UserList users={users} /> |
| 42 | } |
| 43 | ``` |
| 44 | |
| 45 | ### Client Component |
| 46 | |
| 47 | ```tsx |
| 48 | 'use client' |
| 49 | |
| 50 | import { useState } from 'react' |
| 51 | |
| 52 | export function Counter() { |
| 53 | const [count, setCount] = useState(0) |
| 54 | return <button onClick={() => setCount(c => c + 1)}>{count}</button> |
| 55 | } |
| 56 | ``` |
| 57 | |
| 58 | ## Server Actions |
| 59 | |
| 60 | ```tsx |
| 61 | // lib/actions/users.ts |
| 62 | 'use server' |
| 63 | |
| 64 | import { revalidatePath } from 'next/cache' |
| 65 | import { redirect } from 'next/navigation' |
| 66 | |
| 67 | export async function createUser(formData: FormData) { |
| 68 | const email = formData.get('email') as string |
| 69 | |
| 70 | await prisma.user.create({ data: { email } }) |
| 71 | |
| 72 | revalidatePath('/users') |
| 73 | redirect('/users') |
| 74 | } |
| 75 | ``` |
| 76 | |
| 77 | ### Using in Forms |
| 78 | |
| 79 | ```tsx |
| 80 | import { createUser } from '@/lib/actions/users' |
| 81 | |
| 82 | export function CreateUserForm() { |
| 83 | return ( |
| 84 | <form action={createUser}> |
| 85 | <input name="email" type="email" required /> |
| 86 | <button type="submit">Create</button> |
| 87 | </form> |
| 88 | ) |
| 89 | } |
| 90 | ``` |
| 91 | |
| 92 | ## Data Fetching |
| 93 | |
| 94 | ### Parallel Fetching |
| 95 | |
| 96 | ```tsx |
| 97 | export default async function Dashboard() { |
| 98 | const [user, posts] = await Promise.all([ |
| 99 | getUser(), |
| 100 | getPosts() |
| 101 | ]) |
| 102 | |
| 103 | return <DashboardView user={user} posts={posts} /> |
| 104 | } |
| 105 | ``` |
| 106 | |
| 107 | ### Streaming with Suspense |
| 108 | |
| 109 | ```tsx |
| 110 | import { Suspense } from 'react' |
| 111 | |
| 112 | export default function Page() { |
| 113 | return ( |
| 114 | <div> |
| 115 | <h1>Dashboard</h1> |
| 116 | <Suspense fallback={<Loading />}> |
| 117 | <SlowComponent /> |
| 118 | </Suspense> |
| 119 | </div> |
| 120 | ) |
| 121 | } |
| 122 | ``` |
| 123 | |
| 124 | ## Caching |
| 125 | |
| 126 | ```tsx |
| 127 | // Revalidate every 60 seconds |
| 128 | fetch(url, { next: { revalidate: 60 } }) |
| 129 | |
| 130 | // No caching |
| 131 | fetch(url, { cache: 'no-store' }) |
| 132 | |
| 133 | // Static (default) |
| 134 | fetch(url) |
| 135 | ``` |
| 136 | |
| 137 | ## Protected Routes |
| 138 | |
| 139 | ```tsx |
| 140 | // app/(dashboard)/layout.tsx |
| 141 | import { redirect } from 'next/navigation' |
| 142 | import { auth } from '@/lib/auth' |
| 143 | |
| 144 | export default async function DashboardLayout({ children }) { |
| 145 | const session = await auth() |
| 146 | if (!session) redirect('/login') |
| 147 | |
| 148 | return <div>{children}</div> |
| 149 | } |
| 150 | ``` |