Skip to content

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/redis
typescript
// 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 build

Caching 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

  1. Validate all input - Use Zod schemas
  2. Rate limit - Prevent abuse with Upstash
  3. RLS - Database-level access control
  4. Security headers - Protect against common attacks
  5. Optimize queries - Use includes, select only needed fields
  6. Cache aggressively - Redis, ISR, unstable_cache
  7. 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.

← Back to Modules Index