Next.js Cheatsheet
Next.js - The React Framework for Production
Next.js is a React framework that gives you building blocks to create web applications. By framework, we mean Next.js handles the tooling and configuration needed for React, and provides additional structure, features, and optimizations for your application.
Table of Contents
Abschnitt betitelt „Table of Contents“- Installation
- Project Creation
- Project Structure
- Routing
- Pages and Layouts
- API Routes
- Data Fetching
- Styling
- Image Optimization
- Performance Optimization
- Authentication
- Database Integration
- Deployment
- Testing
- Troubleshooting
- Best Practices
Installation
Abschnitt betitelt „Installation“Prerequisites
Abschnitt betitelt „Prerequisites“# Node.js version requirements
node --version # Should be 16.8 or later
# Check npm version
npm --version
# Update npm if needed
npm install -g npm@latest
Create Next.js App
Abschnitt betitelt „Create Next.js App“# Create new Next.js app
npx create-next-app@latest my-app
# Create with TypeScript
npx create-next-app@latest my-app --typescript
# Create with specific template
npx create-next-app@latest my-app --example with-tailwindcss
# Interactive creation
npx create-next-app@latest
Manual Installation
Abschnitt betitelt „Manual Installation“# Create project directory
mkdir my-nextjs-app
cd my-nextjs-app
# Initialize package.json
npm init -y
# Install Next.js, React, and React DOM
npm install next react react-dom
# Install TypeScript (optional)
npm install --save-dev typescript @types/react @types/node
Package.json Scripts
Abschnitt betitelt „Package.json Scripts“{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"export": "next export"
}
}
Project Creation
Abschnitt betitelt „Project Creation“Available Templates
Abschnitt betitelt „Available Templates“# Official examples
npx create-next-app@latest --example api-routes
npx create-next-app@latest --example with-typescript
npx create-next-app@latest --example with-tailwindcss
npx create-next-app@latest --example with-styled-components
npx create-next-app@latest --example with-material-ui
npx create-next-app@latest --example with-redux
npx create-next-app@latest --example with-apollo
npx create-next-app@latest --example with-prisma
npx create-next-app@latest --example with-supabase
npx create-next-app@latest --example with-firebase
Development Server
Abschnitt betitelt „Development Server“# Start development server
npm run dev
# Start on specific port
npm run dev -- -p 3001
# Start with turbo mode (faster builds)
npm run dev -- --turbo
# Start with experimental features
npm run dev -- --experimental-app
Build and Production
Abschnitt betitelt „Build and Production“# Build for production
npm run build
# Start production server
npm run start
# Export static site
npm run export
# Analyze bundle
npm run build -- --analyze
Project Structure
Abschnitt betitelt „Project Structure“App Router Structure (Next.js 13+)
Abschnitt betitelt „App Router Structure (Next.js 13+)“my-nextjs-app/
├── app/ # App Router (new)
│ ├── globals.css # Global styles
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page
│ ├── loading.tsx # Loading UI
│ ├── error.tsx # Error UI
│ ├── not-found.tsx # 404 page
│ ├── about/
│ │ └── page.tsx # /about route
│ ├── blog/
│ │ ├── page.tsx # /blog route
│ │ └── [slug]/
│ │ └── page.tsx # /blog/[slug] route
│ └── api/
│ └── users/
│ └── route.ts # API endpoint
├── components/ # Reusable components
├── lib/ # Utility functions
├── public/ # Static assets
├── styles/ # Additional styles
├── next.config.js # Next.js configuration
├── package.json
└── tsconfig.json
Pages Router Structure (Legacy)
Abschnitt betitelt „Pages Router Structure (Legacy)“my-nextjs-app/
├── pages/ # Pages Router (legacy)
│ ├── _app.tsx # Custom App component
│ ├── _document.tsx # Custom Document
│ ├── index.tsx # Home page (/)
│ ├── about.tsx # About page (/about)
│ ├── blog/
│ │ ├── index.tsx # Blog index (/blog)
│ │ └── [slug].tsx # Dynamic route (/blog/[slug])
│ └── api/
│ └── users.ts # API endpoint (/api/users)
├── components/
├── lib/
├── public/
├── styles/
├── next.config.js
└── package.json
Routing
Abschnitt betitelt „Routing“App Router (Next.js 13+)
Abschnitt betitelt „App Router (Next.js 13+)“File-based Routing
Abschnitt betitelt „File-based Routing“# Route structure
app/
├── page.tsx # / (root)
├── about/page.tsx # /about
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/page.tsx # /blog/[slug]
├── shop/
│ └── [category]/
│ └── [product]/
│ └── page.tsx # /shop/[category]/[product]
└── (dashboard)/ # Route group (doesn't affect URL)
├── analytics/page.tsx # /analytics
└── settings/page.tsx # /settings
Dynamic Routes
Abschnitt betitelt „Dynamic Routes“// app/blog/[slug]/page.tsx
interface PageProps {
params: { slug: string };
searchParams: { [key: string]: string | string[] | undefined };
}
export default function BlogPost({ params, searchParams }: PageProps) {
return <h1>Post: {params.slug}</h1>;
}
// Generate static params
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(res => res.json());
return posts.map((post: any) => ({
slug: post.slug,
}));
}
Catch-all Routes
Abschnitt betitelt „Catch-all Routes“// app/shop/[...slug]/page.tsx
interface PageProps {
params: { slug: string[] };
}
export default function Shop({ params }: PageProps) {
// /shop/a/b/c -> params.slug = ['a', 'b', 'c']
return <div>Shop: {params.slug.join('/')}</div>;
}
Navigation
Abschnitt betitelt „Navigation“// Client-side navigation
import Link from 'next/link';
import { useRouter } from 'next/navigation';
function Navigation() {
const router = useRouter();
return (
<nav>
{/* Static link */}
<Link href="/about">About</Link>
{/* Dynamic link */}
<Link href={`/blog/${post.slug}`}>Read Post</Link>
{/* Programmatic navigation */}
<button onClick={() => router.push('/dashboard')}>
Go to Dashboard
</button>
{/* Replace current entry */}
<button onClick={() => router.replace('/login')}>
Login
</button>
{/* Go back */}
<button onClick={() => router.back()}>
Go Back
</button>
</nav>
);
}
Route Groups and Parallel Routes
Abschnitt betitelt „Route Groups and Parallel Routes“# Route groups (don't affect URL structure)
app/
├── (marketing)/
│ ├── about/page.tsx # /about
│ └── contact/page.tsx # /contact
├── (shop)/
│ ├── products/page.tsx # /products
│ └── cart/page.tsx # /cart
└── page.tsx # /
# Parallel routes
app/
├── @analytics/
│ └── page.tsx
├── @team/
│ └── page.tsx
├── layout.tsx # Can render both slots
└── page.tsx
Pages and Layouts
Abschnitt betitelt „Pages and Layouts“Root Layout
Abschnitt betitelt „Root Layout“// app/layout.tsx
import './globals.css';
export const metadata = {
title: 'My App',
description: 'Generated by Next.js',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<header>
<nav>Navigation</nav>
</header>
<main>{children}</main>
<footer>Footer</footer>
</body>
</html>
);
}
Nested Layouts
Abschnitt betitelt „Nested Layouts“// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="dashboard">
<aside>
<nav>Dashboard Navigation</nav>
</aside>
<main>{children}</main>
</div>
);
}
Page Components
Abschnitt betitelt „Page Components“// app/page.tsx
export default function HomePage() {
return (
<div>
<h1>Welcome to Next.js!</h1>
<p>This is the home page.</p>
</div>
);
}
// With metadata
export const metadata = {
title: 'Home | My App',
description: 'Welcome to my Next.js application',
};
Loading and Error States
Abschnitt betitelt „Loading and Error States“// app/loading.tsx
export default function Loading() {
return (
<div className="loading">
<div className="spinner">Loading...</div>
</div>
);
}
// app/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div className="error">
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
);
}
// app/not-found.tsx
export default function NotFound() {
return (
<div className="not-found">
<h2>Not Found</h2>
<p>Could not find requested resource</p>
</div>
);
}
API Routes
Abschnitt betitelt „API Routes“App Router API Routes
Abschnitt betitelt „App Router API Routes“// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
// GET /api/users
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get('query');
// Fetch users from database
const users = await fetchUsers(query);
return NextResponse.json(users);
}
// POST /api/users
export async function POST(request: NextRequest) {
const body = await request.json();
// Create user
const user = await createUser(body);
return NextResponse.json(user, { status: 201 });
}
// PUT /api/users
export async function PUT(request: NextRequest) {
const body = await request.json();
// Update user
const user = await updateUser(body);
return NextResponse.json(user);
}
// DELETE /api/users
export async function DELETE(request: NextRequest) {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
await deleteUser(id);
return new NextResponse(null, { status: 204 });
}
Dynamic API Routes
Abschnitt betitelt „Dynamic API Routes“// app/api/users/[id]/route.ts
interface RouteParams {
params: { id: string };
}
export async function GET(
request: NextRequest,
{ params }: RouteParams
) {
const user = await fetchUser(params.id);
if (!user) {
return new NextResponse('User not found', { status: 404 });
}
return NextResponse.json(user);
}
export async function DELETE(
request: NextRequest,
{ params }: RouteParams
) {
await deleteUser(params.id);
return new NextResponse(null, { status: 204 });
}
Middleware
Abschnitt betitelt „Middleware“// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Authentication check
const token = request.cookies.get('token');
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
// Add custom headers
const response = NextResponse.next();
response.headers.set('X-Custom-Header', 'value');
return response;
}
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};
API Error Handling
Abschnitt betitelt „API Error Handling“// lib/api-error.ts
export class ApiError extends Error {
constructor(
public statusCode: number,
message: string,
public code?: string
) {
super(message);
this.name = 'ApiError';
}
}
// app/api/users/route.ts
export async function GET() {
try {
const users = await fetchUsers();
return NextResponse.json(users);
} catch (error) {
if (error instanceof ApiError) {
return NextResponse.json(
{ error: error.message, code: error.code },
{ status: error.statusCode }
);
}
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 }
);
}
}
Data Fetching
Abschnitt betitelt „Data Fetching“Server Components (Default)
Abschnitt betitelt „Server Components (Default)“// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
// Revalidate every hour
next: { revalidate: 3600 }
});
if (!res.ok) {
throw new Error('Failed to fetch posts');
}
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<div>
<h1>Posts</h1>
{posts.map((post: any) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
Client Components
Abschnitt betitelt „Client Components“// components/PostList.tsx
'use client';
import { useState, useEffect } from 'react';
export default function PostList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchPosts() {
try {
const res = await fetch('/api/posts');
const data = await res.json();
setPosts(data);
} catch (error) {
console.error('Error fetching posts:', error);
} finally {
setLoading(false);
}
}
fetchPosts();
}, []);
if (loading) return <div>Loading...</div>;
return (
<div>
{posts.map((post: any) => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</div>
);
}
Static Site Generation (SSG)
Abschnitt betitelt „Static Site Generation (SSG)“// app/posts/[slug]/page.tsx
interface Post {
slug: string;
title: string;
content: string;
}
// Generate static params at build time
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(res => res.json());
return posts.map((post: Post) => ({
slug: post.slug,
}));
}
// Generate metadata
export async function generateMetadata({ params }: { params: { slug: string } }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(res => res.json());
return {
title: post.title,
description: post.excerpt,
};
}
export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(res => res.json());
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
Incremental Static Regeneration (ISR)
Abschnitt betitelt „Incremental Static Regeneration (ISR)“// Revalidate every 60 seconds
async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 60 }
});
return res.json();
}
// On-demand revalidation
export async function POST(request: NextRequest) {
const secret = request.nextUrl.searchParams.get('secret');
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ message: 'Invalid secret' }, { status: 401 });
}
try {
await revalidatePath('/posts');
return NextResponse.json({ revalidated: true });
} catch (err) {
return NextResponse.json({ message: 'Error revalidating' }, { status: 500 });
}
}
Styling
Abschnitt betitelt „Styling“CSS Modules
Abschnitt betitelt „CSS Modules“/* styles/Home.module.css */
.container {
padding: 0 2rem;
}
.main {
min-height: 100vh;
padding: 4rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.title {
margin: 0;
line-height: 1.15;
font-size: 4rem;
text-align: center;
}
// components/Home.tsx
import styles from '../styles/Home.module.css';
export default function Home() {
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>Welcome to Next.js!</h1>
</main>
</div>
);
}
Tailwind CSS
Abschnitt betitelt „Tailwind CSS“# Install Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#64748b',
},
},
},
plugins: [],
};
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn-primary {
@apply bg-primary text-white px-4 py-2 rounded hover:bg-blue-600;
}
}
Styled Components
Abschnitt betitelt „Styled Components“# Install styled-components
npm install styled-components
npm install -D @types/styled-components
// components/Button.tsx
import styled from 'styled-components';
const StyledButton = styled.button<{ variant?: 'primary' | 'secondary' }>`
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
cursor: pointer;
font-weight: 500;
${props => props.variant === 'primary' && `
background-color: #3b82f6;
color: white;
&:hover {
background-color: #2563eb;
}
`}
${props => props.variant === 'secondary' && `
background-color: #6b7280;
color: white;
&:hover {
background-color: #4b5563;
}
`}
`;
export default function Button({ children, variant = 'primary', ...props }) {
return (
<StyledButton variant={variant} {...props}>
{children}
</StyledButton>
);
}
CSS-in-JS with Emotion
Abschnitt betitelt „CSS-in-JS with Emotion“# Install Emotion
npm install @emotion/react @emotion/styled
// components/Card.tsx
import styled from '@emotion/styled';
import { css } from '@emotion/react';
const Card = styled.div`
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 1rem;
margin: 1rem 0;
`;
const cardTitle = css`
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 0.5rem;
color: #1f2937;
`;
export default function ProductCard({ title, description }) {
return (
<Card>
<h3 css={cardTitle}>{title}</h3>
<p>{description}</p>
</Card>
);
}
Image Optimization
Abschnitt betitelt „Image Optimization“Next.js Image Component
Abschnitt betitelt „Next.js Image Component“import Image from 'next/image';
export default function Gallery() {
return (
<div>
{/* Static image */}
<Image
src="/hero.jpg"
alt="Hero image"
width={800}
height={600}
priority // Load immediately
/>
{/* Dynamic image */}
<Image
src={`https://api.example.com/images/${imageId}`}
alt="Dynamic image"
width={400}
height={300}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
{/* Responsive image */}
<Image
src="/responsive.jpg"
alt="Responsive image"
fill
style={{ objectFit: 'cover' }}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
);
}
Image Configuration
Abschnitt betitelt „Image Configuration“// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ['example.com', 'api.example.com'],
remotePatterns: [
{
protocol: 'https',
hostname: '**.example.com',
port: '',
pathname: '/images/**',
},
],
formats: ['image/webp', 'image/avif'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
};
module.exports = nextConfig;
Custom Image Loader
Abschnitt betitelt „Custom Image Loader“// lib/imageLoader.ts
export default function cloudinaryLoader({ src, width, quality }) {
const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`];
return `https://res.cloudinary.com/demo/image/fetch/${params.join(',')}/${src}`;
}
// Usage
<Image
loader={cloudinaryLoader}
src="/sample.jpg"
alt="Sample"
width={500}
height={300}
/>
Performance Optimization
Abschnitt betitelt „Performance Optimization“Bundle Analysis
Abschnitt betitelt „Bundle Analysis“# Install bundle analyzer
npm install --save-dev @next/bundle-analyzer
# Configure in next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
// Your Next.js config
});
# Analyze bundle
ANALYZE=true npm run build
Code Splitting
Abschnitt betitelt „Code Splitting“// Dynamic imports
import dynamic from 'next/dynamic';
const DynamicComponent = dynamic(() => import('../components/HeavyComponent'), {
loading: () => <p>Loading...</p>,
ssr: false, // Disable server-side rendering
});
// Conditional loading
const ConditionalComponent = dynamic(
() => import('../components/ConditionalComponent'),
{ ssr: false }
);
export default function Page() {
const [showComponent, setShowComponent] = useState(false);
return (
<div>
<button onClick={() => setShowComponent(true)}>
Load Component
</button>
{showComponent && <ConditionalComponent />}
</div>
);
}
Lazy Loading
Abschnitt betitelt „Lazy Loading“import { lazy, Suspense } from 'react';
const LazyChart = lazy(() => import('../components/Chart'));
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div>Loading chart...</div>}>
<LazyChart />
</Suspense>
</div>
);
}
Performance Monitoring
Abschnitt betitelt „Performance Monitoring“// lib/analytics.ts
export function reportWebVitals(metric) {
switch (metric.name) {
case 'CLS':
case 'FID':
case 'FCP':
case 'LCP':
case 'TTFB':
// Send to analytics service
analytics.track('Web Vital', {
name: metric.name,
value: metric.value,
id: metric.id,
});
break;
default:
break;
}
}
// app/layout.tsx
import { reportWebVitals } from '../lib/analytics';
export { reportWebVitals };
Caching Strategies
Abschnitt betitelt „Caching Strategies“// Static data caching
const getData = cache(async () => {
const res = await fetch('https://api.example.com/data');
return res.json();
});
// Request memoization
import { unstable_cache } from 'next/cache';
const getCachedData = unstable_cache(
async (id) => {
const data = await fetchData(id);
return data;
},
['data-cache'],
{ revalidate: 3600 } // 1 hour
);
Authentication
Abschnitt betitelt „Authentication“NextAuth.js Setup
Abschnitt betitelt „NextAuth.js Setup“# Install NextAuth.js
npm install next-auth
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import CredentialsProvider from 'next-auth/providers/credentials';
const handler = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
CredentialsProvider({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
// Verify credentials
const user = await verifyCredentials(credentials);
return user ? { id: user.id, email: user.email } : null;
}
})
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
async session({ session, token }) {
session.user.id = token.id;
return session;
},
},
pages: {
signIn: '/auth/signin',
signUp: '/auth/signup',
},
});
export { handler as GET, handler as POST };
Session Management
Abschnitt betitelt „Session Management“// components/AuthProvider.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
export default function AuthProvider({
children,
session,
}: {
children: React.ReactNode;
session: any;
}) {
return <SessionProvider session={session}>{children}</SessionProvider>;
}
// app/layout.tsx
import AuthProvider from '../components/AuthProvider';
export default function RootLayout({ children }) {
return (
<html>
<body>
<AuthProvider>
{children}
</AuthProvider>
</body>
</html>
);
}
Protected Routes
Abschnitt betitelt „Protected Routes“// components/ProtectedRoute.tsx
import { useSession } from 'next-auth/react';
import { redirect } from 'next/navigation';
export default function ProtectedRoute({
children,
}: {
children: React.ReactNode;
}) {
const { data: session, status } = useSession();
if (status === 'loading') {
return <div>Loading...</div>;
}
if (!session) {
redirect('/auth/signin');
}
return <>{children}</>;
}
// Server-side protection
import { getServerSession } from 'next-auth';
import { redirect } from 'next/navigation';
export default async function ProtectedPage() {
const session = await getServerSession();
if (!session) {
redirect('/auth/signin');
}
return <div>Protected content</div>;
}
Database Integration
Abschnitt betitelt „Database Integration“Prisma Setup
Abschnitt betitelt „Prisma Setup“# Install Prisma
npm install prisma @prisma/client
npx prisma init
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
Database Operations
Abschnitt betitelt „Database Operations“// lib/posts.ts
import { prisma } from './prisma';
export async function getPosts() {
return await prisma.post.findMany({
include: {
author: {
select: {
name: true,
email: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
});
}
export async function createPost(data: {
title: string;
content: string;
authorId: string;
}) {
return await prisma.post.create({
data,
include: {
author: true,
},
});
}
export async function updatePost(id: string, data: {
title?: string;
content?: string;
published?: boolean;
}) {
return await prisma.post.update({
where: { id },
data,
});
}
export async function deletePost(id: string) {
return await prisma.post.delete({
where: { id },
});
}
API Integration
Abschnitt betitelt „API Integration“// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getPosts, createPost } from '../../../lib/posts';
export async function GET() {
try {
const posts = await getPosts();
return NextResponse.json(posts);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch posts' },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const post = await createPost(body);
return NextResponse.json(post, { status: 201 });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to create post' },
{ status: 500 }
);
}
}
Deployment
Abschnitt betitelt „Deployment“Vercel Deployment
Abschnitt betitelt „Vercel Deployment“# Install Vercel CLI
npm install -g vercel
# Deploy to Vercel
vercel
# Deploy to production
vercel --prod
# Set environment variables
vercel env add VARIABLE_NAME
// vercel.json
{
"framework": "nextjs",
"buildCommand": "npm run build",
"devCommand": "npm run dev",
"installCommand": "npm install",
"functions": {
"app/api/**/*.ts": {
"maxDuration": 30
}
},
"regions": ["iad1"],
"env": {
"DATABASE_URL": "@database-url"
}
}
Docker Deployment
Abschnitt betitelt „Docker Deployment“# Dockerfile
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN yarn build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
Static Export
Abschnitt betitelt „Static Export“// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
trailingSlash: true,
images: {
unoptimized: true
}
};
module.exports = nextConfig;
# Build and export
npm run build
# Serve static files
npx serve out
Testing
Abschnitt betitelt „Testing“Jest and React Testing Library
Abschnitt betitelt „Jest and React Testing Library“# Install testing dependencies
npm install --save-dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom
// jest.config.js
const nextJest = require('next/jest');
const createJestConfig = nextJest({
dir: './',
});
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapping: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^@/pages/(.*)$': '<rootDir>/pages/$1',
},
testEnvironment: 'jest-environment-jsdom',
};
module.exports = createJestConfig(customJestConfig);
// jest.setup.js
import '@testing-library/jest-dom';
Component Testing
Abschnitt betitelt „Component Testing“// __tests__/components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from '../components/Button';
describe('Button', () => {
it('renders button with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('applies correct variant styles', () => {
render(<Button variant="primary">Primary</Button>);
expect(screen.getByRole('button')).toHaveClass('btn-primary');
});
});
API Testing
Abschnitt betitelt „API Testing“// __tests__/api/users.test.ts
import { createMocks } from 'node-mocks-http';
import handler from '../../pages/api/users';
describe('/api/users', () => {
it('returns users list', async () => {
const { req, res } = createMocks({
method: 'GET',
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
const data = JSON.parse(res._getData());
expect(Array.isArray(data)).toBe(true);
});
it('creates new user', async () => {
const { req, res } = createMocks({
method: 'POST',
body: {
name: 'John Doe',
email: 'john@example.com',
},
});
await handler(req, res);
expect(res._getStatusCode()).toBe(201);
const data = JSON.parse(res._getData());
expect(data.name).toBe('John Doe');
});
});
E2E Testing with Playwright
Abschnitt betitelt „E2E Testing with Playwright“# Install Playwright
npm install --save-dev @playwright/test
npx playwright install
// tests/e2e/home.spec.ts
import { test, expect } from '@playwright/test';
test('homepage loads correctly', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/My App/);
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
});
test('navigation works', async ({ page }) => {
await page.goto('/');
await page.getByRole('link', { name: 'About' }).click();
await expect(page).toHaveURL('/about');
await expect(page.getByRole('heading', { name: 'About' })).toBeVisible();
});
Troubleshooting
Abschnitt betitelt „Troubleshooting“Common Issues
Abschnitt betitelt „Common Issues“Hydration Mismatch
Abschnitt betitelt „Hydration Mismatch“// Fix hydration issues
import { useEffect, useState } from 'react';
export default function ClientOnlyComponent() {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
return <div>Client-only content</div>;
}
Memory Leaks
Abschnitt betitelt „Memory Leaks“// Proper cleanup
useEffect(() => {
const subscription = api.subscribe(data => {
setData(data);
});
return () => {
subscription.unsubscribe();
};
}, []);
Build Errors
Abschnitt betitelt „Build Errors“# Clear Next.js cache
rm -rf .next
# Clear node_modules
rm -rf node_modules package-lock.json
npm install
# Check for TypeScript errors
npx tsc --noEmit
Performance Issues
Abschnitt betitelt „Performance Issues“// Optimize re-renders
import { memo, useMemo, useCallback } from 'react';
const ExpensiveComponent = memo(({ data, onUpdate }) => {
const processedData = useMemo(() => {
return data.map(item => ({
...item,
processed: true
}));
}, [data]);
const handleUpdate = useCallback((id) => {
onUpdate(id);
}, [onUpdate]);
return (
<div>
{processedData.map(item => (
<Item key={item.id} data={item} onUpdate={handleUpdate} />
))}
</div>
);
});
Best Practices
Abschnitt betitelt „Best Practices“Project Structure
Abschnitt betitelt „Project Structure“# Recommended structure
src/
├── app/ # App Router pages
├── components/ # Reusable components
│ ├── ui/ # Basic UI components
│ └── features/ # Feature-specific components
├── lib/ # Utility functions
├── hooks/ # Custom hooks
├── types/ # TypeScript types
├── styles/ # Global styles
└── __tests__/ # Test files
Performance Best Practices
Abschnitt betitelt „Performance Best Practices“// Use Server Components by default
export default async function Page() {
const data = await fetchData();
return <div>{data.title}</div>;
}
// Use Client Components only when needed
'use client';
export default function InteractiveComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
// Optimize images
<Image
src="/hero.jpg"
alt="Hero"
width={800}
height={600}
priority
placeholder="blur"
/>
SEO Best Practices
Abschnitt betitelt „SEO Best Practices“// Metadata API
export const metadata = {
title: 'My Page',
description: 'Page description',
keywords: ['next.js', 'react', 'javascript'],
authors: [{ name: 'John Doe' }],
openGraph: {
title: 'My Page',
description: 'Page description',
images: ['/og-image.jpg'],
},
twitter: {
card: 'summary_large_image',
title: 'My Page',
description: 'Page description',
images: ['/twitter-image.jpg'],
},
};
// Dynamic metadata
export async function generateMetadata({ params }) {
const post = await fetchPost(params.id);
return {
title: post.title,
description: post.excerpt,
};
}
Security Best Practices
Abschnitt betitelt „Security Best Practices“// Environment variables
const apiKey = process.env.API_KEY; // Server-side only
const publicApiUrl = process.env.NEXT_PUBLIC_API_URL; // Client-side accessible
// Content Security Policy
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline';"
}
];
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
];
},
};
Summary
Abschnitt betitelt „Summary“Next.js is a powerful React framework that provides a complete solution for building modern web applications. Key features include:
- App Router: New routing system with layouts, loading states, and error boundaries
- Server Components: Render components on the server for better performance
- API Routes: Built-in API endpoints with full-stack capabilities
- Image Optimization: Automatic image optimization and lazy loading
- Performance: Built-in optimizations for Core Web Vitals
- Deployment: Seamless deployment to Vercel and other platforms
For optimal results, leverage Server Components by default, use Client Components sparingly, implement proper caching strategies, and follow Next.js conventions for routing and data fetching.