0
# Next.js Implementation Patterns
1
2
Production-ready patterns for common use cases.
3
4
## Authentication
5
6
### Server-Side Auth Check
7
```typescript
8
// app/dashboard/page.tsx
9
import { cookies } from 'next/headers';
10
import { redirect } from 'next/navigation';
11
12
export default async function Dashboard() {
13
const cookieStore = await cookies();
14
const token = cookieStore.get('token');
15
16
if (!token) redirect('/login');
17
18
const user = await getUser(token.value);
19
return <div>Welcome {user.name}</div>;
20
}
21
```
22
23
### Middleware Auth
24
```typescript
25
// middleware.ts
26
import { NextResponse } from 'next/server';
27
import type { NextRequest } from 'next/server';
28
29
export function middleware(request: NextRequest) {
30
const token = request.cookies.get('token');
31
32
if (!token) {
33
return NextResponse.redirect(new URL('/login', request.url));
34
}
35
36
return NextResponse.next();
37
}
38
39
export const config = {
40
matcher: ['/dashboard/:path*', '/admin/:path*'],
41
};
42
```
43
44
## Data Fetching
45
46
### Parallel Data Fetching
47
```typescript
48
export default async function Dashboard() {
49
const [user, posts, analytics] = await Promise.all([
50
getUser(),
51
getPosts(),
52
getAnalytics(),
53
]);
54
55
return (
56
<div>
57
<UserProfile user={user} />
58
<PostsList posts={posts} />
59
<Analytics data={analytics} />
60
</div>
61
);
62
}
63
```
64
65
### Streaming with Suspense
66
```typescript
67
import { Suspense } from 'react';
68
69
export default function Page() {
70
return (
71
<div>
72
<Suspense fallback={<div>Loading quick...</div>}>
73
<QuickComponent />
74
</Suspense>
75
76
<Suspense fallback={<div>Loading slow...</div>}>
77
<SlowComponent />
78
</Suspense>
79
</div>
80
);
81
}
82
```
83
84
## Form Handling
85
86
### Server Actions
87
```typescript
88
// app/actions.ts
89
'use server';
90
import { revalidatePath, redirect } from 'next/navigation';
91
92
export async function createPost(formData: FormData) {
93
const title = formData.get('title');
94
const post = await savePost({ title });
95
96
revalidatePath('/posts');
97
redirect(`/posts/${post.id}`);
98
}
99
100
// app/posts/create/page.tsx
101
import { createPost } from '../actions';
102
103
export default function CreatePost() {
104
return (
105
<form action={createPost}>
106
<input name="title" />
107
<button type="submit">Create</button>
108
</form>
109
);
110
}
111
```
112
113
### Optimistic Updates
114
```typescript
115
'use client';
116
import { useTransition, useState } from 'react';
117
118
export default function LikeButton({
119
postId,
120
initialCount
121
}: {
122
postId: string;
123
initialCount: number;
124
}) {
125
const [isPending, startTransition] = useTransition();
126
const [count, setCount] = useState<number>(initialCount);
127
128
function handleLike() {
129
startTransition(async () => {
130
setCount(prev => prev + 1); // Optimistic
131
await likePost(postId);
132
});
133
}
134
135
return (
136
<button onClick={handleLike} disabled={isPending}>
137
Like ({count})
138
</button>
139
);
140
}
141
```
142
143
## Search & Filters
144
145
### URL Search Params
146
```typescript
147
'use client';
148
import { useRouter, useSearchParams } from 'next/navigation';
149
150
export default function SearchBar() {
151
const router = useRouter();
152
const searchParams = useSearchParams();
153
154
function handleSearch(query: string) {
155
const params = new URLSearchParams(searchParams.toString());
156
params.set('q', query);
157
router.push(`/search?${params.toString()}`);
158
}
159
160
return (
161
<input
162
onChange={(e) => handleSearch(e.target.value)}
163
defaultValue={searchParams.get('q') || ''}
164
/>
165
);
166
}
167
```
168
169
### Server-Side Search
170
```typescript
171
interface SearchResult {
172
id: string;
173
title: string;
174
}
175
176
export default async function SearchPage({
177
searchParams,
178
}: {
179
searchParams: Promise<{ q?: string }>;
180
}) {
181
const { q } = await searchParams;
182
const results: SearchResult[] = q ? await searchItems(q) : [];
183
184
return <SearchResults results={results} />;
185
}
186
```
187
188
## Caching & Revalidation
189
190
### On-Demand Revalidation
191
```typescript
192
// app/api/revalidate/route.ts
193
import { revalidatePath } from 'next/cache';
194
import { NextRequest, NextResponse } from 'next/server';
195
196
export async function POST(request: NextRequest) {
197
const { secret, path } = await request.json();
198
199
if (secret !== process.env.REVALIDATE_SECRET) {
200
return NextResponse.json({ error: 'Invalid' }, { status: 401 });
201
}
202
203
revalidatePath(path);
204
return NextResponse.json({ revalidated: true });
205
}
206
```
207
208
### Tag-Based Revalidation
209
```typescript
210
// Fetch with tag
211
const posts = await fetch('https://api.example.com/posts', {
212
next: { tags: ['posts', `post-${postId}`] },
213
});
214
215
// Revalidate in Server Action
216
'use server';
217
import { revalidateTag } from 'next/cache';
218
219
export async function updatePost(postId: string) {
220
await savePost(postId);
221
revalidateTag(`post-${postId}`);
222
}
223
```
224
225
## API Routes
226
227
### REST API
228
```typescript
229
// app/api/users/[id]/route.ts
230
import { NextRequest, NextResponse } from 'next/server';
231
232
export async function GET(
233
request: NextRequest,
234
{ params }: { params: Promise<{ id: string }> }
235
) {
236
const { id } = await params;
237
const user = await getUser(id);
238
239
if (!user) {
240
return NextResponse.json({ error: 'Not found' }, { status: 404 });
241
}
242
243
return NextResponse.json(user);
244
}
245
246
export async function PATCH(
247
request: NextRequest,
248
{ params }: { params: Promise<{ id: string }> }
249
) {
250
const { id } = await params;
251
const body = await request.json();
252
const user = await updateUser(id, body);
253
return NextResponse.json(user);
254
}
255
```
256
257
## Image Patterns
258
259
### Responsive Images
260
```typescript
261
import Image from 'next/image';
262
263
<Image
264
src="/photo.jpg"
265
alt="Photo"
266
width={800}
267
height={600}
268
sizes="(max-width: 768px) 100vw, 50vw"
269
priority
270
/>
271
```
272
273
### Dynamic Images from API
274
```typescript
275
import Image from 'next/image';
276
277
export default async function PostImage({ imageUrl }: { imageUrl: string }) {
278
return (
279
<Image
280
src={imageUrl}
281
alt="Post image"
282
width={800}
283
height={600}
284
/>
285
);
286
}
287
```
288
289
## Loading States
290
291
### Page-Level Loading
292
```typescript
293
// app/posts/loading.tsx
294
export default function Loading() {
295
return <div className="animate-pulse">Loading...</div>;
296
}
297
```
298
299
### Skeleton Screens
300
```typescript
301
export default function PostSkeleton() {
302
return (
303
<div className="animate-pulse space-y-4">
304
<div className="h-8 bg-gray-200 rounded w-3/4"></div>
305
<div className="h-4 bg-gray-200 rounded w-full"></div>
306
</div>
307
);
308
}
309
```
310
311
## Error Boundaries
312
313
```typescript
314
// app/error.tsx
315
'use client';
316
import { useEffect } from 'react';
317
318
export default function Error({
319
error,
320
reset
321
}: {
322
error: Error & { digest?: string };
323
reset: () => void;
324
}) {
325
useEffect(() => {
326
console.error('Error:', error);
327
}, [error]);
328
329
return (
330
<div>
331
<h2>Something went wrong!</h2>
332
<button onClick={reset}>Try again</button>
333
</div>
334
);
335
}
336
```
337