Module 12: Security & Performance
Production-ready apps need security hardening and performance optimization.
Security Checklist
- [ ] HTTPS everywhere
- [ ] Input validation (Zod)
- [ ] Authentication on all protected routes
- [ ] Authorization (users can only access their data)
- [ ] Rate limiting
- [ ] SQL injection prevention (use ORM)
- [ ] XSS prevention (React escapes by default)
- [ ] CSRF protection
- [ ] Security headers
- [ ] Secrets in environment variables
- [ ] Dependency updates
Input Validation
Always validate user input with Zod:
typescript
import { z } from 'zod'
const createUserSchema = z.object({
email: z.string().email('Invalid email'),
name: z.string().min(2).max(100),
password: z.string().min(8).max(100),
})
export async function POST(request: Request) {
const body = await request.json()
const result = createUserSchema.safeParse(body)
if (!result.success) {
return Response.json(
{ error: 'Validation failed', details: result.error.flatten() },
{ status: 400 }
)
}
// Use result.data - it's typed and validated
const user = await createUser(result.data)
return Response.json(user)
}Rate Limiting
With Upstash
bash
npm install @upstash/ratelimit @upstash/redistypescript
// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
})
export const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 requests per 10 seconds
analytics: true,
})typescript
// middleware.ts or API route
import { ratelimit } from '@/lib/rate-limit'
export async function POST(request: Request) {
const ip = request.headers.get('x-forwarded-for') ?? 'anonymous'
const { success, limit, remaining, reset } = await ratelimit.limit(ip)
if (!success) {
return new Response('Too Many Requests', {
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
'X-RateLimit-Reset': reset.toString(),
},
})
}
// Process request...
}Row Level Security (RLS)
PostgreSQL RLS ensures data isolation at database level:
sql
-- Enable RLS on table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- Users can only see their own posts
CREATE POLICY "Users can view own posts"
ON posts
FOR SELECT
USING (author_id = auth.uid());
-- Users can only insert as themselves
CREATE POLICY "Users can insert own posts"
ON posts
FOR INSERT
WITH CHECK (author_id = auth.uid());
-- Users can only update own posts
CREATE POLICY "Users can update own posts"
ON posts
FOR UPDATE
USING (author_id = auth.uid());
-- Users can only delete own posts
CREATE POLICY "Users can delete own posts"
ON posts
FOR DELETE
USING (author_id = auth.uid());Security Headers
typescript
// next.config.js
const securityHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin',
},
]
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
]
},
}Performance Optimization
Caching
typescript
// Static generation (built at build time)
export const dynamic = 'force-static'
// ISR (regenerate every 60 seconds)
export const revalidate = 60
// Dynamic (no caching)
export const dynamic = 'force-dynamic'Database Query Optimization
typescript
// Bad: N+1 query
const posts = await db.post.findMany()
for (const post of posts) {
const author = await db.user.findUnique({ where: { id: post.authorId } })
}
// Good: Single query with include
const posts = await db.post.findMany({
include: { author: true },
})
// Good: Select only needed fields
const posts = await db.post.findMany({
select: {
id: true,
title: true,
author: {
select: { name: true },
},
},
})Image Optimization
tsx
import Image from 'next/image'
// Next.js automatically optimizes
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // Load immediately for LCP
/>
// Lazy load below-fold images
<Image
src="/feature.jpg"
alt="Feature"
width={600}
height={400}
loading="lazy"
/>Code Splitting
tsx
// Lazy load heavy components
import dynamic from 'next/dynamic'
const HeavyChart = dynamic(() => import('@/components/chart'), {
loading: () => <p>Loading chart...</p>,
ssr: false, // Don't render on server
})Bundle Analysis
bash
npm install @next/bundle-analyzer
# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({})
# Run analysis
ANALYZE=true npm run buildCaching Strategies
Server-Side Caching
typescript
import { unstable_cache } from 'next/cache'
const getCachedUser = unstable_cache(
async (userId: string) => {
return db.user.findUnique({ where: { id: userId } })
},
['user'],
{ revalidate: 3600, tags: ['user'] } // Cache for 1 hour
)
// Usage
const user = await getCachedUser('user_123')
// Invalidate
import { revalidateTag } from 'next/cache'
revalidateTag('user')Redis Caching
typescript
import { Redis } from '@upstash/redis'
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
})
async function getExpensiveData(key: string) {
// Try cache first
const cached = await redis.get(key)
if (cached) return cached
// Compute expensive data
const data = await computeExpensiveData()
// Cache for 1 hour
await redis.set(key, data, { ex: 3600 })
return data
}Monitoring
Error Tracking
typescript
// lib/sentry.ts
import * as Sentry from '@sentry/nextjs'
export function captureError(error: Error, context?: Record<string, any>) {
Sentry.captureException(error, { extra: context })
}
// Usage
try {
await riskyOperation()
} catch (error) {
captureError(error, { userId: user.id })
throw error
}Performance Monitoring
typescript
// Track custom metrics
import { track } from '@vercel/analytics'
track('api_response_time', {
endpoint: '/api/users',
duration: 150,
})Summary
- Validate all input - Use Zod schemas
- Rate limit - Prevent abuse with Upstash
- RLS - Database-level access control
- Security headers - Protect against common attacks
- Optimize queries - Use includes, select only needed fields
- Cache aggressively - Redis, ISR, unstable_cache
- Monitor - Sentry for errors, analytics for performance
Congratulations! You've completed all 12 modules. You now have the knowledge to build a production-ready SaaS application.