Mastering CORS in Next.js: A Comprehensive Guide to Secure Cross-Origin Requests
What is CORS?
Cross-Origin Resource Sharing (CORS) is a crucial security mechanism that browsers implement to protect users from potentially malicious cross-origin requests. It's essentially an HTTP-header based protocol that enables a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources.
💡 Key Point: CORS is not a security feature that prevents attacks - it's a browser mechanism that helps manage cross-origin requests securely.
Common CORS Scenarios:
- Frontend app (localhost:3000) calling an API (api.example.com)
- Microservices architecture with multiple domains
- Mobile apps accessing web services
- Third-party API integrations
CORS in Next.js
Next.js, being a full-stack framework, requires proper CORS configuration when:
- Building API routes that other applications will consume
- Implementing BFF (Backend-for-Frontend) patterns
- Creating serverless functions
- Handling client-side API calls to different domains
// Example of a problematic cross-origin request async function fetchData() { const response = await fetch('https://api.example.com/data'); const data = await response.json(); return data; }
Prerequisites
Before implementing CORS in Next.js, ensure you understand:
- [ ] Basic Next.js project structure
- [ ] API routes in Next.js
- [ ] HTTP headers and status codes
- [ ] Security implications of CORS
- [ ] Your application's cross-origin requirements
Implementation Methods
1. Using headers Configuration
The most straightforward approach is using the Next.js config file:
// next.config.js module.exports = { async headers() { return [ { source: '/api/:path*', headers: [ { key: 'Access-Control-Allow-Origin', value: '*' // Configure this based on your needs }, { key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE,OPTIONS' }, { key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization' } ] } ]; } };
2. Using Middleware
// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const response = NextResponse.next(); // Configure CORS headers response.headers.set('Access-Control-Allow-Origin', '*'); response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization'); return response; } export const config = { matcher: '/api/:path*' };
3. Vercel Configuration
{ "headers": [ { "source": "/api/(.*)", "headers": [ { "key": "Access-Control-Allow-Credentials", "value": "true" }, { "key": "Access-Control-Allow-Origin", "value": "*" }, { "key": "Access-Control-Allow-Methods", "value": "GET,DELETE,PATCH,POST,PUT" }, { "key": "Access-Control-Allow-Headers", "value": "Accept, Authorization, Content-Type" } ] } ] }
Enhanced Security
Cross-Origin-Opener-Policy
// next.config.js module.exports = { async headers() { return [ { source: '/:path*', headers: [ { key: 'Cross-Origin-Opener-Policy', value: 'same-origin' } ] } ]; } };
Strict Origin Policy
// pages/api/[...route].ts export default function handler(req, res) { res.setHeader( 'Referrer-Policy', 'strict-origin-when-cross-origin' ); // Your API logic here }
Real-world Examples
1. Basic API Route with CORS
// pages/api/data.ts import type { NextApiRequest, NextApiResponse } from 'next'; import cors from 'cors'; // Middleware setup const corsMiddleware = cors({ origin: process.env.ALLOWED_ORIGIN, methods: ['GET', 'POST'] }); export default async function handler( req: NextApiRequest, res: NextApiResponse ) { await new Promise((resolve) => corsMiddleware(req, res, resolve)); if (req.method === 'GET') { return res.status(200).json({ message: 'Data fetched successfully' }); } return res.status(405).json({ message: 'Method not allowed' }); }
2. Environment-based CORS Configuration
// utils/cors.ts export const getCorsHeaders = (env: string) => { const headers = new Map(); switch(env) { case 'development': headers.set('Access-Control-Allow-Origin', '*'); break; case 'production': headers.set('Access-Control-Allow-Origin', process.env.PRODUCTION_ORIGIN); break; default: headers.set('Access-Control-Allow-Origin', process.env.DEFAULT_ORIGIN); } return Object.fromEntries(headers); };
Best Practices
- Never use * in production
- Always specify exact origins
- Use environment variables for configuration
- Implement proper error handling
try { // Your API logic } catch (error) { res.status(500).json({ error: 'Internal Server Error', details: process.env.NODE_ENV === 'development' ? error.message : undefined }); }
- Regular security audits
- Review CORS configurations
- Monitor cross-origin requests
- Update allowed origins as needed
Conclusion
CORS in Next.js requires careful consideration of security implications and proper implementation. By following these guidelines and examples, you can create secure, scalable applications that safely handle cross-origin requests.
⚠️ Remember: Always test CORS configurations thoroughly in a staging environment before deploying to production.