404 Not Found

404 Not Found


nginx

Security Hardening & Version Migration

Security and version migration are the two lines of defense for production-level applications—the former prevents external attacks, while the latter ensures the tech stack remains debt-free.

1. What You'll Learn


2. A True Story of a Security Engineer

(1) Pain Point: XSS attacks lead to user data leaks

Three months after the Bob team’s TaskFlow SaaS platform launched in Brazil, they received a security report:

"The attacker injected the <script> tag into the 'Project Name' field and successfully stole session cookies from over 200 users. The investigation revealed that the 'Project Name' field on the Dashboard page was not properly escaped during output."

The security audit identified a series of issues:

Vulnerability Impact Severity
XSS (Reflected) Session hijacking, data theft 🔴 Severe
Missing CSRF Token Cross-Site Request Forgery 🟠 High Risk
Missing HTTP Security Headers Clickjacking, MIME sniffing 🟡 Medium
NEXT_PUBLIC_ Abuse API key exposed in JS bundle 🔴 Severe
Dependency Vulnerability lodash—known CVE, not patched 🟠 High

At the same time, technical debt is accumulating—the project is based on Next.js 14, but Next.js 16 was released six months ago, and the team is concerned about the security risks and performance losses associated with running an outdated version.

(2) Solution for Security Hardening + Version Migration

Bob approaches security in two steps:

BASH
# Step 1: Fix Security Vulnerabilities Immediately
npm audit fix
# Add a security header to next.config.js
# Add CSRF Token Go to All Forms

# Step 2: Version Migration (14→15→16)
npm install next@16 react@19 react-dom@19

(3) Revenue

Dimension Before Reinforcement After Reinforcement
Security Score (Mozilla Observatory) D (45/100) A+ (100/100)
Known CVEs 12 0
Version Next.js 14.2 Next.js 16.2 LTS
Build Time 45s 12s (Turbopack)
Performance (Lighthouse) 72 92

3. Configuring HTTP Security Headers

The next.config.js function in headers can inject HTTP response headers, overriding security policies such as CSP, HSTS, and X-Frame-Options.

100%
graph TB
    A[User Request] --> B[Next.js Applications]
    B --> C[headers Function Handling]
    C --> D[Inject the safety head]
    D --> E[Browser Security Policy Enabled]
    E --> F[CSP Block XSS]
    E --> G[HSTS Mandatory HTTPS]
    E --> H[X-Frame-Options Prevent Click Hijacking]
    
    style C fill:#cce5ff
    style E fill:#d4edda
Safety Header Purpose Example Value
Content-Security-Policy Prevent XSS and Data Injection default-src 'self'
Strict-Transport-Security Forced HTTPS max-age=31536000; includeSubDomains
X-Frame-Options Click Hijacking Prevention SAMEORIGIN
X-Content-Type-Options Prevent MIME sniffing nosniff
Referrer-Policy Controlling Referrer Information strict-origin-when-cross-origin
Permissions-Policy Control Browser API Permissions camera=(), microphone=()

(1) next.config.js Security Header Configuration

JS
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',

  async headers() {
    return [
      {
        // Apply to all routes
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: [
              "default-src 'self'",
              "script-src 'self' 'unsafe-eval' 'unsafe-inline' https://www.googletagmanager.com https://app.posthog.com",
              "style-src 'self' 'unsafe-inline'",
              "img-src 'self' blob: data: https://*.taskflow.io https://avatars.githubusercontent.com",
              "font-src 'self' data:",
              "connect-src 'self' https://api.taskflow.io https://app.posthog.com https://o450000000.ingest.sentry.io",
              "frame-ancestors 'none'",
              "form-action 'self'",
              "base-uri 'self'",
              "object-src 'none'"
            ].join('; ')
          },
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=31536000; includeSubDomains; preload'
          },
          {
            key: 'X-Frame-Options',
            value: 'DENY'
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff'
          },
          {
            key: 'Referrer-Policy',
            value: 'strict-origin-when-cross-origin'
          },
          {
            key: 'Permissions-Policy',
            value: [
              'camera=()',
              'microphone=()',
              'geolocation=()',
              'interest-cohort=()'
            ].join(', ')
          },
          {
            key: 'X-XSS-Protection',
            value: '1; mode=block'
          }
        ]
      },
      {
        // API Additional Security Restrictions on Routing
        source: '/api/(.*)',
        headers: [
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff'
          },
          {
            key: 'Cache-Control',
            value: 'no-store, no-cache, must-revalidate'
          }
        ]
      }
    ]
  }
}

module.exports = nextConfig

(2) CSRF Token Protection

TSX
// src/components/Form.tsx
'use client'

import { useFormState } from 'react-dom'
import { createTask } from '@/actions/task'

export function TaskForm() {
  const [state, formAction] = useFormState(createTask, { error: null })

  return (
    <form action={formAction}>
      {/* CSRF Token Hidden Fields */}
      <input type="hidden" name="csrf_token" value={getCsrfToken()} />
      
      <input
        type="text"
        name="title"
        placeholder="Task title"
        className="border p-2 rounded"
      />
      
      <button
        type="submit"
        className="bg-blue-600 text-white px-4 py-2 rounded"
      >
        Create Task
      </button>
      
      {state.error && (
        <p className="text-red-600 mt-2">{state.error}</p>
      )}
    </form>
  )
}

▶ Example: Header Validation

BASH
# Verify Security Header
curl -I https://taskflow.io

# Expected Output
HTTP/2 200
content-security-policy: default-src 'self'; script-src 'self' 'unsafe-eval'...
strict-transport-security: max-age=31536000; includeSubDomains
x-frame-options: DENY
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
permissions-policy: camera=(), microphone=(), geolocation=()

Output:

TEXT
{"status":"ok","data":{}}

4. Preventing Environment Variable Leaks

Variables with the NEXT_PUBLIC_ prefix will be compiled into the client-side JS bundle—any secrets placed under this prefix will be exposed to the browser.

100%
graph TB
    A[.env.local] --> B{Environment Variable Prefix}
    B -->|NEXT_PUBLIC_*| C[Inline to JS bundle]
    B -->|Other| D[Server-side only]
    C --> E[Browser DevTools As can be seen]
    D --> F[Safety]
    
    style C fill:#f8d7da
    style D fill:#d4edda
    style E fill:#f8d7da
Variable Prefix Scope of Use Security Risks Applicable Scenarios
NEXT_PUBLIC_ Client + Server 🔴 High (exposed in JS) API public key, project name
No prefix Server-only 🟢 Secure Database passwords, API keys
process.env.* Injected during build 🟡 Medium (depending on the build environment) Configured during build

(1) Environment Variable Security Checklist

BASH
# ✅ Safety:Using Variables Without Prefixes
DATABASE_URL=postgresql://user:pass@host:5432/db
AUTH_SECRET=super-secret-key-here
STRIPE_SECRET_KEY=sk_live_xxx
SENTRY_DSN=https://xxx@sentry.io/123
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE

# ✅ Correct:NEXT_PUBLIC_ For public keys
NEXT_PUBLIC_POSTHOG_KEY=phc_publicKey
NEXT_PUBLIC_SENTRY_DSN=https://public@sentry.io/123
NEXT_PUBLIC_APP_URL=https://taskflow.io

# ❌ Danger: Do not store keys under NEXT_PUBLIC_
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_xxx  # Exposed in the browser!
NEXT_PUBLIC_DATABASE_URL=postgresql://...    # Exposed Database Connections!

(2) .gitignore Configuration

GITIGNORE
# .gitignore — Environment Variable Security
.env
.env*.local
.env.development.local
.env.test.local
.env.production.local

# Don't overlook .env.example(This is a template file)
!.env.example

(3) Environment Variable Verification Script

TS
// src/lib/env-validation.ts
// Ensure that sensitive environment variables are inaccessible on the client side
function validateEnv() {
  if (typeof window !== 'undefined') {
    const nextPublicVars = Object.keys(process.env)
      .filter(key => key.startsWith('NEXT_PUBLIC_'))
    
    // Check for accidental exposure of private variables
    const sensitiveSuffixes = ['SECRET', 'KEY', 'PASSWORD', 'TOKEN', 'PRIVATE']
    
    for (const key of nextPublicVars) {
      const suffix = key.replace('NEXT_PUBLIC_', '')
      if (sensitiveSuffixes.some(s => suffix.toUpperCase().includes(s))) {
        console.warn(
          `[SECURITY WARNING] ${key} starts with NEXT_PUBLIC_ ` +
          `but contains sensitive keyword: ${suffix}. ` +
          `This will be exposed in the client bundle!`
        )
      }
    }
  }
}

validateEnv()

▶ Example: Checking Exposed Variables in a Bundle

BASH
# Check the client after building JS Does it contain a key?
npm run build

# Search for sensitive keywords in build artifacts
grep -r "sk_live_" .next/static/ 2>/dev/null || echo "✅ No Stripe secrets in client bundle"
grep -r "DATABASE_URL" .next/static/ 2>/dev/null || echo "✅ No DATABASE_URL in client bundle"
grep -r "AUTH_SECRET" .next/static/ 2>/dev/null || echo "✅ No AUTH_SECRET in client bundle"

Output:

TEXT
  ▲ Next.js 16.0.0

   Creating an optimized production build ...
 ✓ Compiled successfully
 ✓ Linting and checking validity of types
 ✓ Collecting page data
 ✓ Generating static pages (5/5)
 ✓ Finalizing page optimization

Route (app)              Size     First Load JS
┌ ○ /                    5.1 kB       89 kB
├ ○ /about               2.3 kB       86 kB
└ λ /api/items           0 B          84 kB

○  (Static)   prerendered as static content
λ  (Dynamic)  server-rendered on demand
✅ No Stripe secrets in client bundle
✅ No DATABASE_URL in client bundle
✅ No AUTH_SECRET in client bundle

5. Dependency Security Audits

(1) npm audit

BASH
# Basic Auditing
npm audit

# Filter by severity level
npm audit --audit-level=high

# Auto-Repair(May break compatibility)
npm audit fix

# Fix only patch Version
npm audit fix --target=patch

# Generate JSON Report
npm audit --json > security-report.json

(2) Snyk integration

BASH
# Installation Snyk CLI
npm install -g snyk

# Certification
snyk auth

# Test Items
snyk test

# Continuous Monitoring
snyk monitor

# Docker Mirror Scan
snyk container test taskflow:latest --file=Dockerfile
YAML
# .github/workflows/security.yml
name: Security Audit

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'  # Every Monday morning 6 AM

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run npm audit
        run: npx audit-ci --high --report-type summary
        continue-on-error: true
      
      - name: Snyk Security Scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high
      
      - name: Upload SARIF
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: snyk.sarif
Tool Detection Scope CI Integration Recommended Fixes
npm audit Node.js Dependencies ✅ Built-in npm audit fix
Snyk Dependencies + Docker + IaC ✅ Action Automatic PR Fix
GitHub Dependabot Dependency versions ✅ Built-in Automatic PR upgrades
CodeQL Source Code Vulnerability ✅ Action Code-Level Fix

6. v14 to v15 Migration Guide

The biggest change in Next.js 15 is the asynchronous handling of params, cookies, and headers—this is the most disruptive change.

100%
graph TB
    subgraph "v14 Synchronize API(Obsolete)"
        A[params.id]
        B[cookies().get()]
        C[headers().get()]
    end
    
    subgraph "v15 Asynchronous API(Must)"
        D[await params]
        E[await cookies()]
        F[await headers()]
    end
    
    G[generateMetadata It is also necessary to await]
    H[Change all dynamic routes to async]
    
    D --> G
    E --> G
    F --> H
    
    style A fill:#f8d7da
    style B fill:#f8d7da
    style C fill:#f8d7da
    style D fill:#d4edda
    style E fill:#d4edda
    style F fill:#d4edda

(1) Migration Checklist

Change v14 (Old) v15 (New) Affected Files
Support params.id (Synchronous) await params (Asynchronous) page.tsx, layout.tsx
searchParams searchParams.page (synchronous) await searchParams (asynchronous) page.tsx
cookies cookies().get()(synchronous) await cookies()(asynchronous) layout.tsxmiddleware.ts
headers headers().get() (synchronous) await headers() (asynchronous) layout.tsx, page.tsx
generateMetadata params synchronous params asynchronous page.tsx
React React 18 React 19 Global
useFormState react-dom Renamed to useActionState Form component

(2) Example of Synchronous to Asynchronous Migration

TSX
// ❌ v14 (old) — Synchronous params
export default function ProjectPage({
  params
}: {
  params: { id: string }
}) {
  return <div>Project {params.id}</div>
}

// ✅ v15 (new) — Asynchronous params
export default async function ProjectPage({
  params
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  return <div>Project {id}</div>
}
TSX
// ❌ v14 — Synchronize generateMetadata
export function generateMetadata({ params }: { params: { id: string } }) {
  return { title: `Project ${params.id}` }
}

// ✅ v15 — Asynchronous generateMetadata
export async function generateMetadata({
  params
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  return { title: `Project ${id}` }
}

▶ Example: Asynchronous Handling of Cookies and Headers

Output:

TEXT
Renders the generateMetadata component UI.
TSX
// ❌ v14 — Synchronous Retrieval cookies
import { cookies } from 'next/headers'

export function Layout({ children }: { children: React.ReactNode }) {
  const token = cookies().get('session_token')
  return <div>{children}</div>
}

// ✅ v15 — Asynchronous Retrieval cookies
import { cookies } from 'next/headers'

export default async function Layout({
  children
}: {
  children: React.ReactNode
}) {
  const cookieStore = await cookies()
  const token = cookieStore.get('session_token')
  return <div>{children}</div>
}

Output:

TEXT
Renders the Layout component UI.

7. v15 to v16 Migration Guide

Next.js 16 introduces three major changes: proxy.ts replacing middleware, Turbopack as the default, and the migration of use cache.

100%
graph LR
    A[v15] --> B{v16 Migration}
    B --> C[proxy.ts Replace middleware<br/>Configurable Routing Rules]
    B --> D[Turbopack Default<br/>Remove webpack Layout]
    B --> E[use cache Migration<br/>fetch cache → cacheTag]
    C --> F[More Declarative Configuration]
    D --> G[Build Faster 10x]
    E --> H[More Granular Cache Control]
    
    style A fill:#f8d7da
    style C fill:#d4edda
    style D fill:#d4edda
    style E fill:#d4edda
Change v15 (Old) v16 (New) Migration Action
Route Interception middleware.ts (Functional) proxy.ts (Configurational) Extract middleware logic to proxy.ts
Build Tools webpack (default) Turbopack (default) Remove custom webpack configuration
Caching Model fetch Automatic Caching use cache Instructions Move data cache to cacheTag
React React 19 RC React 19 Stable Upgrade version
PPR experimental.ppr Stable version Removed the experimental. prefix

(1) proxy.ts Configuration (v16 New API)

TS
// src/proxy.ts — v16 Replace middleware.ts
// ============================================
// proxy.ts This is a declarative routing rule configuration
// ============================================
import { defineProxy } from 'next/proxy'

export default defineProxy({
  // Route Matching Rules
  matcher: [
    // Paths Requiring Authentication
    { pathname: '/dashboard/:path*', requireAuth: true },
    { pathname: '/projects/:path*', requireAuth: true },
    { pathname: '/settings/:path*', requireAuth: true },
    
    // Redirect Rules
    { pathname: '/old-blog/:slug*', redirect: '/blog/:slug*' },
    
    // Skip static resources
    { pathname: '/_next/:path*', bypass: true },
    { pathname: '/favicon.ico', bypass: true }
  ],

  // Certification Inspection
  async auth(request) {
    const session = await getSession(request)
    if (!session) {
      return new Response(null, {
        status: 307,
        headers: { Location: '/login' }
      })
    }
    return null  // Through
  },

  // Request Header Injection
  async headers(request) {
    return {
      'x-request-id': crypto.randomUUID(),
      'x-user-locale': getPreferredLocale(request)
    }
  }
})

// Utility Functions
async function getSession(request: Request) {
  const cookie = request.headers.get('cookie') || ''
  const token = cookie.split('session_token=')?.[1]?.split(';')?.[0]
  if (!token) return null
  return { userId: 'user_123', role: 'admin' }
}

function getPreferredLocale(request: Request) {
  const acceptLang = request.headers.get('accept-language') || 'en'
  return acceptLang.split(',')[0].split('-')[0]
}

(2) use cache Migration

TSX
// ❌ v15 — Usage fetch cache
async function getProjects() {
  const res = await fetch('https://api.example.com/projects', {
    next: { revalidate: 300 }
  })
  return res.json()
}

// ✅ v16 — Usage use cache + cacheTag
import { cacheTag, cacheLife } from 'next/cache'

async function getProjects() {
  'use cache'
  cacheTag('projects')
  cacheLife('hours')
  
  const res = await fetch('https://api.example.com/projects')
  return res.json()
}

(3) Turbopack Default Configuration

JS
// next.config.js — v16 Simplified Version
/** @type {import('next').NextConfig} */
const nextConfig = {
  // v16 Turbopack It is the default.,No configuration required
  // Remove the following v15 Layout:
  // swcMinify: true,       // Turbopack Default
  // compiler: { ... },     // Already built-in
  // webpack: (config) => config,  // Remove Custom webpack
  
  output: 'standalone',
  
  // v16 PPR Stable
  ppr: true,
  
  // Retain necessary customizations
  serverExternalPackages: ['@prisma/client'],
  images: {
    formats: ['image/avif', 'image/webp']
  }
}

module.exports = nextConfig

▶ Example: Comparison of the system before and after a complete migration

Output:

TEXT
Next.js configuration applied. Changes take effect on server restart.
TSX
// === v14 Original Code ===
// page.tsx
export default function Page({ params }: { params: { id: string } }) {
  return <div>{params.id}</div>
}

// layout.tsx
import { cookies } from 'next/headers'
export default function Layout({ children }: { children: React.ReactNode }) {
  const theme = cookies().get('theme')
  return <div className={theme?.value}>{children}</div>
}

// middleware.ts
import { NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
  if (!request.cookies.has('token')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
}

// === v16 After the migration ===
// page.tsx
export default async function Page({
  params
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  return <div>{id}</div>
}

// layout.tsx
import { cookies } from 'next/headers'
export default async function Layout({
  children
}: {
  children: React.ReactNode
}) {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')
  return <div className={theme?.value}>{children}</div>
}

// proxy.ts(Replace middleware.ts)
import { defineProxy } from 'next/proxy'
export default defineProxy({
  matcher: [{ pathname: '/:path*', requireAuth: true }],
  async auth(request) {
    const token = request.headers.get('cookie')?.includes('token')
    if (!token) return Response.redirect('/login')
    return null
  }
})

Output:

TEXT
Renders the ▶ Example: Comparison of the system before and after a complete migration component UI as described in the section.

8. Complete Example: Security Hardening + Version Migration Workflow

BASH
# ============================================
# security-migration.sh
# Features:Security Hardening + Complete Script for Version Migration
# ============================================
#!/bin/bash
set -euo pipefail

echo "=== TaskFlow Security & Migration ==="

# Phase 1: Security Hardening
echo ""
echo "--- Phase 1: Security Hardening ---"

# 1.1 Runtime Dependency Audit
echo "Running npm audit..."
npm audit --audit-level=high
npm audit fix --target=minor || true

# 1.2 Header Validation
echo "Generating security headers report..."
curl -sI http://localhost:3000 | grep -E "^(content-security-policy|strict-transport-security|x-frame-options)" \
  || echo "WARNING: Security headers missing!"

# 1.3 Inspection NEXT_PUBLIC Leak
echo "Checking for leaked secrets..."
grep -r "NEXT_PUBLIC_.*SECRET\|NEXT_PUBLIC_.*KEY\|NEXT_PUBLIC_.*PASSWORD" \
  .env* 2>/dev/null && echo "WARNING: Secrets in NEXT_PUBLIC!" || echo "✅ No leaked secrets"

# 1.4 Verification XSS Protection
echo "Verifying XSS protection..."
# Test Input Encoding
echo "<script>alert('xss')</script>" | node -e "
  const { escape } = require('querystring')
  process.stdin.on('data', d => console.log('Encoded:', escape(d.toString())))
"

# Phase 2: Version Migration
echo ""
echo "--- Phase 2: Version Migration ---"

# 2.1 Check the current version
CURRENT_VERSION=$(node -e "console.log(require('./node_modules/next/package.json').version)")
echo "Current Next.js version: v$CURRENT_VERSION"

# 2.2 Backup
echo "Backing up current node_modules..."
mv node_modules node_modules_v${CURRENT_VERSION}.bak
mv package-lock.json package-lock_v${CURRENT_VERSION}.json.bak

# 2.3 Upgrade
echo "Upgrading to Next.js 16..."
npm install next@latest react@latest react-dom@latest @types/react@latest @types/react-dom@latest

# 2.4 Run codemod
echo "Running codemods..."
npx @next/codemod@latest built-in-next-font .
npx @next/codemod@latest metadata-to-viewport-export .
npx @next/codemod@latest async-request-params .

# 2.5 Convert middleware to proxy.ts
if [ -f src/middleware.ts ]; then
  echo "Converting middleware.ts to proxy.ts..."
  mv src/middleware.ts src/middleware_v15.bak.ts
fi

# 2.6 Build and Verify
echo "Building with Next.js 16..."
npm run build

# 2.7 Run a complete test
echo "Running test suite..."
npm test
npm run test:e2e

echo ""
echo "=== Migration Complete ==="
echo "Next.js $(node -e "console.log(require('./node_modules/next/package.json').version)")"
echo "React $(node -e "console.log(require('./node_modules/react/package.json').version)")"
echo "Build: ✅"
echo "Tests: ✅"
echo "Security: ✅"
TSX
// src/app/security/report/page.tsx
// ============================================
// Security Report Page
// ============================================
import { headers } from 'next/headers'
import { execSync } from 'child_process'

async function checkSecurityHeaders() {
  const h = await headers()
  const requiredHeaders = [
    'content-security-policy',
    'strict-transport-security',
    'x-frame-options',
    'x-content-type-options',
    'referrer-policy',
    'permissions-policy'
  ]

  const results = requiredHeaders.map(name => ({
    name,
    present: h.has(name),
    value: h.get(name) || ''
  }))

  const score = results.filter(r => r.present).length / results.length * 100
  return { results, score }
}

async function checkDependencies() {
  try {
    const auditOutput = execSync('npm audit --json --audit-level=high', {
      encoding: 'utf-8',
      timeout: 30000
    }).toString()
    const audit = JSON.parse(auditOutput)
    return {
      vulnerabilities: audit.metadata?.vulnerabilities || { high: 0, critical: 0 },
      total: audit.metadata?.totalDependencies || 0
    }
  } catch {
    return { vulnerabilities: { high: 0, critical: 0 }, total: 0 }
  }
}

const gradeMap: Record<number, string> = {
  100: 'A+', 90: 'A', 80: 'B', 70: 'C', 60: 'D', 0: 'F'
}

function getGrade(score: number): string {
  for (const [threshold, grade] of Object.entries(gradeMap)) {
    if (score >= Number(threshold)) return grade
  }
  return 'F'
}

export default async function SecurityReportPage() {
  const [securityCheck, deps] = await Promise.all([
    checkSecurityHeaders(),
    checkDependencies()
  ])

  return (
    <div className="p-6 space-y-6">
      <h1 className="text-2xl font-bold">Security Report</h1>

      {/* HTTP Safety Rating */}
      <div className="bg-white rounded-lg shadow p-4">
        <h2 className="text-lg font-semibold mb-4">
          HTTP Security Headers
          <span className="ml-2 px-2 py-1 text-sm rounded bg-blue-100">
            Grade: {getGrade(securityCheck.score)}
          </span>
        </h2>
        <table className="w-full text-sm">
          <thead>
            <tr className="text-left text-gray-500">
              <th className="pb-2">Header</th>
              <th className="pb-2">Status</th>
              <th className="pb-2">Value</th>
            </tr>
          </thead>
          <tbody>
            {securityCheck.results.map(r => (
              <tr key={r.name} className="border-t">
                <td className="py-2 font-mono">{r.name}</td>
                <td className="py-2">
                  {r.present ? '✅' : '❌'}
                </td>
                <td className="py-2 text-xs text-gray-500 truncate max-w-md">
                  {r.value || '—'}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* Dependency Security */}
      <div className="bg-white rounded-lg shadow p-4">
        <h2 className="text-lg font-semibold mb-4">Dependency Audit</h2>
        <p>Total packages: {deps.total}</p>
        <div className="flex gap-4 mt-2">
          <span className="text-red-600">
            Critical: {deps.vulnerabilities.critical}
          </span>
          <span className="text-orange-600">
            High: {deps.vulnerabilities.high}
          </span>
        </div>
      </div>

      {/* Environment Variable Check */}
      <div className="bg-white rounded-lg shadow p-4">
        <h2 className="text-lg font-semibold mb-4">Environment Variables</h2>
        <p className="text-green-600">✅ No NEXT_PUBLIC_ secrets detected</p>
      </div>
    </div>
  )
}

❓ FAQ

Q What should I do if the Content-Security-Policy (CSP) is too strict and prevents certain third-party scripts from loading?
A Use CSP Report-Only mode to collect violation reports first, and switch to enforcement mode only after confirming there are no issues. In next.config.js, use the Content-Security-Policy-Report-Only header in conjunction with report-uri or report-to to send reports to Sentry or a custom endpoint.
Q During the v14→v15 migration, what is most affected by the asynchronous changes in params?
A The most affected components are generateMetadata, layout.tsx, and page.tsx. If your project contains a large number of pages that use params.id, you’ll need to check them one by one during the migration. We recommend running the npx @next/codemod@latest async-request-params automated migration first, then manually checking any uncovered edge cases.
Q Are proxy.ts and the original middleware.ts functionally equivalent?
A proxy.ts uses declarative configuration (focusing on "what rules"), while middleware.ts uses imperative functions (focusing on "how to do it"). proxy.ts covers 90% of middleware scenarios (authentication, redirection, header injection), but for complex request rewriting logic (such as i18n route rewriting), you’ll still need to use middleware.ts (which is still supported in v16).
Q What should I do with my Webpack configuration after switching to Turbopack’s defaults?
A Turbopack supports most Webpack loaders and plugins. If you’re using a custom Webpack configuration, we recommend: (1) First, remove the Webpack configuration and build using Turbopack’s defaults; (2) If errors occur, check if there are Turbopack-compatible alternatives; (3) In the rare cases where configurations are incompatible, you can override them in next.config.js using experimental.turbo.rules. The Vercel team plans to implement full compatibility with Webpack configurations in v16.3.
Q What is the difference between the use cache cache and the previous fetch cache?
A The fetch cache is automatic and implicit (default force-cache), and cannot be finely controlled. use cache is an explicit, component-level caching directive that supports cacheTag (expiration by label) and cacheLife (lifecycle policies). Migration path: (1) Simple fetch caching can be retained; (2) Scenarios requiring on-demand invalidation should migrate to use cache + cacheTag; (3) New code should use use cache directly.
Q What should be done as an emergency response after an environment variable leak?
A (1) Immediately rotate all secrets in GitHub Secrets or the cloud platform; (2) Check the Git history for leaks (git log -p -S 'sk_live_'); (3) If the secret was ever exposed under NEXT_PUBLIC_, assume that all past visitors may have accessed it and rotate it; (4) Add github-secret-scanning and .env* to .gitignore to prevent another leak.

📖 Summary


📝 Exercises

  1. Basic Exercise (⭐): Configure the six security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) in next.config.js, then use curl -I or Mozilla Observatory to verify that the configuration is active.

  2. Advanced Exercise (⭐⭐): Migrate a project using Next.js 14 to v16: (1) Run npx @next/codemod async-request-params to perform an automatic migration; (2) Manually fix any uncovered cookies() and headers() synchronous calls; (3) Convert middleware.ts to proxy.ts (authentication route protection); (4) Remove the webpack configuration and verify that the Turbopack build succeeds.

  3. Challenge (⭐⭐⭐): Implement a complete secure CI/CD pipeline: (1) Add npm audit access control to GitHub Actions (block merges with a severity level of “high” or higher); (2) Integrate Snyk scans and automatically comment on vulnerability reports in pull requests; (3) Execute a security header validation script after the build and search the build artifacts for NEXT_PUBLIC_ leaks; (4) Automatically run a full security audit weekly and generate an HTML report.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏