or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

api.mdconcepts.mdindex.mdpages-router.mdpatterns.mdspecial-files.md

patterns.mddocs/

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