[{"data":1,"prerenderedAt":477},["ShallowReactive",2],{"blog-best-practices/nextjs":3},{"id":4,"title":5,"body":6,"category":452,"date":453,"dateModified":454,"description":455,"draft":456,"extension":457,"faq":458,"featured":456,"headerVariant":463,"image":464,"keywords":464,"meta":465,"navigation":466,"ogDescription":467,"ogTitle":464,"path":414,"readTime":468,"schemaOrg":469,"schemaType":470,"seo":471,"sitemap":472,"stem":473,"tags":474,"twitterCard":475,"__hash__":476},"blog/blog/best-practices/nextjs.md","Next.js Security Best Practices: API Routes, Auth, and Data Protection",{"type":7,"value":8,"toc":438},"minimark",[9,20,29,34,37,110,114,117,132,141,145,148,157,161,164,173,181,185,188,197,201,204,213,217,220,229,233,236,245,249,321,350,378,382,385,407,426],[10,11,12],"tldr",{},[13,14,15,19],"p",{},[16,17,18],"strong",{},"The #1 Next.js security best practice is understanding and respecting the server/client boundary."," These 7 practices take about 30 minutes to implement and prevent 84% of security issues found in Next.js applications. Focus on: keeping secrets in server-only code, protecting API routes with authentication middleware, using Server Actions carefully, and adding security headers.",[21,22,23],"quotable-box",{},[24,25,26],"blockquote",{},[13,27,28],{},"\"In Next.js, security is architecture. Know what runs where, and you'll know what's safe where.\"",[30,31,33],"h2",{"id":32},"understanding-the-serverclient-boundary-3-min","Understanding the Server/Client Boundary 3 min",[13,35,36],{},"Next.js runs code on both server and client. Security issues often arise from confusing which code runs where.",[38,39,40,56],"table",{},[41,42,43],"thead",{},[44,45,46,50,53],"tr",{},[47,48,49],"th",{},"Code Location",[47,51,52],{},"Runs On",[47,54,55],{},"Can Access Secrets?",[57,58,59,71,80,89,99],"tbody",{},[44,60,61,65,68],{},[62,63,64],"td",{},"Server Components",[62,66,67],{},"Server only",[62,69,70],{},"Yes",[44,72,73,76,78],{},[62,74,75],{},"API Routes / Route Handlers",[62,77,67],{},[62,79,70],{},[44,81,82,85,87],{},[62,83,84],{},"Server Actions",[62,86,67],{},[62,88,70],{},[44,90,91,94,97],{},[62,92,93],{},"Middleware",[62,95,96],{},"Edge/Server",[62,98,70],{},[44,100,101,104,107],{},[62,102,103],{},"Client Components ('use client')",[62,105,106],{},"Browser",[62,108,109],{},"No (only NEXT_PUBLIC_)",[30,111,113],{"id":112},"best-practice-1-protect-environment-variables-2-min","Best Practice 1: Protect Environment Variables 2 min",[13,115,116],{},"Next.js exposes variables with NEXT_PUBLIC_ prefix to the browser. Never put secrets in these:",[118,119,121],"code-block",{"label":120},".env.local (correct usage)",[122,123,128],"pre",{"className":124,"code":126,"language":127},[125],"language-text","# Server-side only (safe for secrets)\nDATABASE_URL=postgresql://user:pass@host/db\nSTRIPE_SECRET_KEY=sk_live_xxx\nJWT_SECRET=your-secret-key\n\n# Client-side exposed (only public values)\nNEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co\nNEXT_PUBLIC_APP_URL=https://yourdomain.com\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_xxx\n","text",[129,130,126],"code",{"__ignoreMap":131},"",[133,134,135],"warning-box",{},[13,136,137,140],{},[16,138,139],{},"Critical:"," Any NEXT_PUBLIC_ variable is bundled into client JavaScript and visible to users. Never put API keys, database URLs, or secrets in NEXT_PUBLIC_ variables.",[30,142,144],{"id":143},"best-practice-2-secure-api-routes-5-min","Best Practice 2: Secure API Routes 5 min",[13,146,147],{},"API routes (App Router route handlers) need authentication and validation:",[118,149,151],{"label":150},"app/api/user/route.ts",[122,152,155],{"className":153,"code":154,"language":127},[125],"import { NextRequest, NextResponse } from 'next/server';\nimport { getServerSession } from 'next-auth';\nimport { z } from 'zod';\n\nconst updateSchema = z.object({\n  name: z.string().min(2).max(100),\n  email: z.string().email(),\n});\n\nexport async function GET(request: NextRequest) {\n  // Check authentication\n  const session = await getServerSession();\n  if (!session?.user) {\n    return NextResponse.json(\n      { error: 'Unauthorized' },\n      { status: 401 }\n    );\n  }\n\n  // Return only user's own data\n  const userData = await db.user.findUnique({\n    where: { id: session.user.id },\n    select: { id: true, name: true, email: true }\n  });\n\n  return NextResponse.json(userData);\n}\n\nexport async function PUT(request: NextRequest) {\n  const session = await getServerSession();\n  if (!session?.user) {\n    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n  }\n\n  // Validate input\n  const body = await request.json();\n  const result = updateSchema.safeParse(body);\n\n  if (!result.success) {\n    return NextResponse.json(\n      { error: 'Invalid input', issues: result.error.issues },\n      { status: 400 }\n    );\n  }\n\n  // Update user's own data only\n  await db.user.update({\n    where: { id: session.user.id },\n    data: result.data,\n  });\n\n  return NextResponse.json({ success: true });\n}\n",[129,156,154],{"__ignoreMap":131},[30,158,160],{"id":159},"best-practice-3-secure-server-actions-5-min","Best Practice 3: Secure Server Actions 5 min",[13,162,163],{},"Server Actions are powerful but need careful security handling:",[118,165,167],{"label":166},"Secure Server Action pattern",[122,168,171],{"className":169,"code":170,"language":127},[125],"'use server';\n\nimport { getServerSession } from 'next-auth';\nimport { revalidatePath } from 'next/cache';\nimport { z } from 'zod';\n\nconst postSchema = z.object({\n  title: z.string().min(3).max(200),\n  content: z.string().max(50000),\n});\n\nexport async function createPost(formData: FormData) {\n  // Always verify authentication\n  const session = await getServerSession();\n  if (!session?.user) {\n    throw new Error('Unauthorized');\n  }\n\n  // Validate input\n  const rawData = {\n    title: formData.get('title'),\n    content: formData.get('content'),\n  };\n\n  const result = postSchema.safeParse(rawData);\n  if (!result.success) {\n    return { error: 'Invalid input' };\n  }\n\n  // Create with verified user ID\n  await db.post.create({\n    data: {\n      ...result.data,\n      authorId: session.user.id, // Never trust client-provided user ID\n    },\n  });\n\n  revalidatePath('/posts');\n  return { success: true };\n}\n",[129,172,170],{"__ignoreMap":131},[133,174,175],{},[13,176,177,180],{},[16,178,179],{},"Important:"," Server Actions can be called directly by any client. Always validate authentication and never trust any data passed from the client.",[30,182,184],{"id":183},"best-practice-4-use-middleware-for-route-protection-5-min","Best Practice 4: Use Middleware for Route Protection 5 min",[13,186,187],{},"Middleware runs before every request and is ideal for authentication:",[118,189,191],{"label":190},"middleware.ts",[122,192,195],{"className":193,"code":194,"language":127},[125],"import { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\nimport { getToken } from 'next-auth/jwt';\n\nexport async function middleware(request: NextRequest) {\n  const token = await getToken({ req: request });\n  const isAuthPage = request.nextUrl.pathname.startsWith('/login');\n  const isProtectedRoute = request.nextUrl.pathname.startsWith('/dashboard');\n  const isAdminRoute = request.nextUrl.pathname.startsWith('/admin');\n\n  // Redirect logged-in users away from auth pages\n  if (isAuthPage && token) {\n    return NextResponse.redirect(new URL('/dashboard', request.url));\n  }\n\n  // Protect dashboard routes\n  if (isProtectedRoute && !token) {\n    return NextResponse.redirect(new URL('/login', request.url));\n  }\n\n  // Admin routes require admin role\n  if (isAdminRoute) {\n    if (!token || token.role !== 'admin') {\n      return NextResponse.redirect(new URL('/dashboard', request.url));\n    }\n  }\n\n  return NextResponse.next();\n}\n\nexport const config = {\n  matcher: ['/dashboard/:path*', '/admin/:path*', '/login'],\n};\n",[129,196,194],{"__ignoreMap":131},[30,198,200],{"id":199},"best-practice-5-add-security-headers-3-min","Best Practice 5: Add Security Headers 3 min",[13,202,203],{},"Configure security headers in next.config.js:",[118,205,207],{"label":206},"next.config.js",[122,208,211],{"className":209,"code":210,"language":127},[125],"/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  async headers() {\n    return [\n      {\n        source: '/(.*)',\n        headers: [\n          {\n            key: 'X-Content-Type-Options',\n            value: 'nosniff',\n          },\n          {\n            key: 'X-Frame-Options',\n            value: 'DENY',\n          },\n          {\n            key: 'X-XSS-Protection',\n            value: '1; mode=block',\n          },\n          {\n            key: 'Referrer-Policy',\n            value: 'strict-origin-when-cross-origin',\n          },\n          {\n            key: 'Permissions-Policy',\n            value: 'camera=(), microphone=(), geolocation=()',\n          },\n        ],\n      },\n    ];\n  },\n};\n\nmodule.exports = nextConfig;\n",[129,212,210],{"__ignoreMap":131},[30,214,216],{"id":215},"best-practice-6-prevent-data-leaks-in-server-components-3-min","Best Practice 6: Prevent Data Leaks in Server Components 3 min",[13,218,219],{},"Server Components can accidentally expose sensitive data:",[118,221,223],{"label":222},"Safe data fetching in Server Components",[122,224,227],{"className":225,"code":226,"language":127},[125],"// app/dashboard/page.tsx\nimport { getServerSession } from 'next-auth';\nimport { redirect } from 'next/navigation';\n\nexport default async function DashboardPage() {\n  const session = await getServerSession();\n\n  if (!session) {\n    redirect('/login');\n  }\n\n  // Fetch only current user's data\n  const userData = await db.user.findUnique({\n    where: { id: session.user.id },\n    select: {\n      id: true,\n      name: true,\n      email: true,\n      // Never select: password, tokens, etc.\n    },\n  });\n\n  return (\n    \u003CDashboard user={userData} />\n  );\n}\n",[129,228,226],{"__ignoreMap":131},[30,230,232],{"id":231},"best-practice-7-secure-file-uploads-5-min","Best Practice 7: Secure File Uploads 5 min",[13,234,235],{},"Handle file uploads carefully in Next.js API routes:",[118,237,239],{"label":238},"Secure file upload handler",[122,240,243],{"className":241,"code":242,"language":127},[125],"import { NextRequest, NextResponse } from 'next/server';\nimport { getServerSession } from 'next-auth';\n\nconst ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];\nconst MAX_SIZE = 5 * 1024 * 1024; // 5MB\n\nexport async function POST(request: NextRequest) {\n  const session = await getServerSession();\n  if (!session) {\n    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n  }\n\n  const formData = await request.formData();\n  const file = formData.get('file') as File;\n\n  if (!file) {\n    return NextResponse.json({ error: 'No file provided' }, { status: 400 });\n  }\n\n  // Validate file type\n  if (!ALLOWED_TYPES.includes(file.type)) {\n    return NextResponse.json({ error: 'Invalid file type' }, { status: 400 });\n  }\n\n  // Validate file size\n  if (file.size > MAX_SIZE) {\n    return NextResponse.json({ error: 'File too large' }, { status: 400 });\n  }\n\n  // Generate safe filename\n  const ext = file.name.split('.').pop();\n  const safeFilename = `${crypto.randomUUID()}.${ext}`;\n\n  // Upload to secure storage (S3, etc.)\n  // ...\n\n  return NextResponse.json({ success: true, filename: safeFilename });\n}\n",[129,244,242],{"__ignoreMap":131},[30,246,248],{"id":247},"common-nextjs-security-mistakes","Common Next.js Security Mistakes",[38,250,251,264],{},[41,252,253],{},[44,254,255,258,261],{},[47,256,257],{},"Mistake",[47,259,260],{},"Impact",[47,262,263],{},"Prevention",[57,265,266,277,288,299,310],{},[44,267,268,271,274],{},[62,269,270],{},"Secrets in NEXT_PUBLIC_ vars",[62,272,273],{},"Credential exposure",[62,275,276],{},"Use server-only env vars",[44,278,279,282,285],{},[62,280,281],{},"Unprotected API routes",[62,283,284],{},"Unauthorized access",[62,286,287],{},"Add auth to every route",[44,289,290,293,296],{},[62,291,292],{},"Trusting Server Action input",[62,294,295],{},"Data manipulation",[62,297,298],{},"Always validate and verify auth",[44,300,301,304,307],{},[62,302,303],{},"Exposing user data in props",[62,305,306],{},"Information disclosure",[62,308,309],{},"Select only needed fields",[44,311,312,315,318],{},[62,313,314],{},"Missing security headers",[62,316,317],{},"XSS, clickjacking",[62,319,320],{},"Configure in next.config.js",[322,323,324],"info-box",{},[13,325,326,329,330,337,338,343,344,349],{},[16,327,328],{},"Official Resources:"," For the latest information, see ",[331,332,336],"a",{"href":333,"rel":334},"https://nextjs.org/docs/app/building-your-application/authentication",[335],"nofollow","Next.js Authentication Documentation",", ",[331,339,342],{"href":340,"rel":341},"https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations",[335],"Server Actions Guide",", and ",[331,345,348],{"href":346,"rel":347},"https://nextjs.org/docs/app/api-reference/next-config-js/headers",[335],"Security Headers Configuration",".",[351,352,353,360,366,372],"faq-section",{},[354,355,357],"faq-item",{"question":356},"Are Server Components secure by default?",[13,358,359],{},"Server Components run on the server so they can access secrets safely. However, data passed to Client Components as props is serialized and sent to the browser, so be careful what you pass down.",[354,361,363],{"question":362},"How do I protect API routes in App Router?",[13,364,365],{},"Use getServerSession or your auth library in each route handler to verify authentication. Consider creating a reusable auth wrapper function to reduce repetition and ensure consistent protection.",[354,367,369],{"question":368},"Are Server Actions safe to use?",[13,370,371],{},"Server Actions are secure for the server-side operations they perform, but they can be called by any client with any data. Always authenticate the user and validate all inputs within the action itself.",[354,373,375],{"question":374},"Should I use Middleware or API route checks for auth?",[13,376,377],{},"Use both. Middleware provides a first line of defense and handles redirects. API routes should still verify authentication because Middleware can be bypassed in some edge cases.",[30,379,381],{"id":380},"further-reading","Further Reading",[13,383,384],{},"Put these practices into action with our step-by-step guides.",[386,387,388,395,401],"ul",{},[389,390,391],"li",{},[331,392,394],{"href":393},"/blog/how-to/add-security-headers","Add security headers to your app",[389,396,397],{},[331,398,400],{"href":399},"/blog/checklists/pre-deployment-security-checklist","Pre-deployment security checklist",[389,402,403],{},[331,404,406],{"href":405},"/blog/getting-started/first-scan","Run your first security scan",[408,409,410,416,421],"related-articles",{},[411,412],"related-card",{"description":413,"href":414,"title":415},"Complete security guide","/blog/best-practices/nextjs","Next.js Security Guide",[411,417],{"description":418,"href":419,"title":420},"Pre-launch checklist","/blog/checklists/nextjs-security-checklist","Next.js Checklist",[411,422],{"description":423,"href":424,"title":425},"Frontend security patterns","/blog/best-practices/react","React Best Practices",[427,428,431,435],"cta-box",{"href":429,"label":430},"/","Start Free Scan",[30,432,434],{"id":433},"verify-your-nextjs-security","Verify Your Next.js Security",[13,436,437],{},"Scan your Next.js project for security issues and misconfigurations.",{"title":131,"searchDepth":439,"depth":439,"links":440},2,[441,442,443,444,445,446,447,448,449,450,451],{"id":32,"depth":439,"text":33},{"id":112,"depth":439,"text":113},{"id":143,"depth":439,"text":144},{"id":159,"depth":439,"text":160},{"id":183,"depth":439,"text":184},{"id":199,"depth":439,"text":200},{"id":215,"depth":439,"text":216},{"id":231,"depth":439,"text":232},{"id":247,"depth":439,"text":248},{"id":380,"depth":439,"text":381},{"id":433,"depth":439,"text":434},"best-practices","2026-01-29","2026-02-16","Complete Next.js security best practices. Learn to secure API routes, protect environment variables, implement authentication, and deploy safely.",false,"md",[459,460,461,462],{"question":356,"answer":359},{"question":362,"answer":365},{"question":368,"answer":371},{"question":374,"answer":377},"nextjs",null,{},true,"Master Next.js security with API route protection, auth patterns, and environment variable handling.","15 min read","[object Object]","Article",{"title":5,"description":455},{"loc":414},"blog/best-practices/nextjs",[],"summary_large_image","6lG0SLeLJBqcSWhWHEt4_LQ7c2GHGuJlpI7tZ1YJuJ8",1775843918547]