Implement Next.js middleware (proxy.js) for request interception, authentication, redirects, and request/response modification before routes render.
Implement Next.js middleware using the `proxy.js` file convention to intercept and modify HTTP requests before routes are rendered. Perfect for authentication, logging, redirects, rewrites, and custom server-side logic.
Creates Next.js middleware (proxy) that runs on the server before requests complete. You can:
Create `proxy.ts` (or `proxy.js`) in your project root (same level as `pages` or `app` directory). If using `src` directory, place it inside `src/`.
**TypeScript Example:**
```typescript
import { NextResponse, NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
// Your middleware logic here
return NextResponse.next()
}
// Optional: Configure path matching
export const config = {
matcher: '/dashboard/:path*',
}
```
**JavaScript Example:**
```javascript
import { NextResponse } from 'next/server'
export function proxy(request) {
// Your middleware logic here
return NextResponse.next()
}
export const config = {
matcher: '/dashboard/:path*',
}
```
Use the `matcher` option to specify which paths trigger the middleware:
**Single path:**
```typescript
export const config = {
matcher: '/about',
}
```
**Multiple paths:**
```typescript
export const config = {
matcher: ['/about', '/dashboard/:path*', '/api/:path*'],
}
```
**Exclude paths with regex:**
```typescript
export const config = {
matcher: [
// Exclude API routes, static files, and images
'/((?!api|_next/static|_next/image|.*\\.png$).*)',
],
}
```
**Advanced matching with conditions:**
```typescript
export const config = {
matcher: [
{
source: '/api/:path*',
locale: false,
has: [
{ type: 'header', key: 'Authorization', value: 'Bearer Token' },
{ type: 'query', key: 'userId' },
],
missing: [{ type: 'cookie', key: 'session' }],
},
],
}
```
**Authentication Check:**
```typescript
import { NextResponse, NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
const token = request.cookies.get('auth-token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/settings/:path*'],
}
```
**Add Custom Headers:**
```typescript
export function proxy(request: NextRequest) {
const response = NextResponse.next()
response.headers.set('X-Custom-Header', 'my-value')
response.headers.set('X-Request-ID', crypto.randomUUID())
return response
}
```
**Conditional Redirects:**
```typescript
export function proxy(request: NextRequest) {
const country = request.geo?.country || 'US'
if (country === 'FR' && !request.nextUrl.pathname.startsWith('/fr')) {
return NextResponse.redirect(new URL('/fr', request.url))
}
return NextResponse.next()
}
```
**Set Cookies:**
```typescript
export function proxy(request: NextRequest) {
const response = NextResponse.next()
response.cookies.set('visited', 'true', {
maxAge: 60 * 60 * 24 * 365, // 1 year
httpOnly: true,
})
return response
}
```
**Rewrite URLs:**
```typescript
export function proxy(request: NextRequest) {
// Rewrite /old-page to /new-page without changing the URL
if (request.nextUrl.pathname === '/old-page') {
return NextResponse.rewrite(new URL('/new-page', request.url))
}
return NextResponse.next()
}
```
**CORS Headers:**
```typescript
export function proxy(request: NextRequest) {
const response = NextResponse.next()
response.headers.set('Access-Control-Allow-Origin', '*')
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')
// Handle preflight
if (request.method === 'OPTIONS') {
return new NextResponse(null, { status: 204, headers: response.headers })
}
return response
}
```
**Direct Response:**
```typescript
export function proxy(request: NextRequest) {
const apiKey = request.headers.get('x-api-key')
if (!apiKey || apiKey !== process.env.API_KEY) {
return new NextResponse(
JSON.stringify({ error: 'Unauthorized' }),
{ status: 401, headers: { 'Content-Type': 'application/json' } }
)
}
return NextResponse.next()
}
```
```typescript
export function proxy(request: NextRequest) {
// URL information
const url = request.nextUrl
const pathname = url.pathname
const searchParams = url.searchParams
// Headers
const userAgent = request.headers.get('user-agent')
const referer = request.headers.get('referer')
// Cookies
const sessionCookie = request.cookies.get('session')
// Geo location (on Edge Runtime)
const country = request.geo?.country
const city = request.geo?.city
// IP address
const ip = request.ip
return NextResponse.next()
}
```
Middleware executes in this order:
1. `headers` from `next.config.js`
2. `redirects` from `next.config.js`
3. **Middleware (proxy.js)** ← Your code runs here
4. `beforeFiles` rewrites from `next.config.js`
5. Filesystem routes (`public/`, `_next/static/`, `pages/`, `app/`)
6. `afterFiles` rewrites from `next.config.js`
7. Dynamic Routes
8. `fallback` rewrites from `next.config.js`
If you've customized `pageExtensions` in `next.config.js`:
```javascript
// next.config.js
module.exports = {
pageExtensions: ['page.ts', 'page.js'],
}
```
Name your middleware file accordingly:
```
proxy.page.ts // or proxy.page.js
```
```typescript
import { NextResponse, NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value
const { pathname } = request.nextUrl
// Public paths that don't require authentication
const publicPaths = ['/login', '/signup', '/']
const isPublicPath = publicPaths.some(path => pathname.startsWith(path))
// User is not authenticated
if (!token && !isPublicPath) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('redirect', pathname)
return NextResponse.redirect(loginUrl)
}
// User is authenticated but accessing login page
if (token && pathname.startsWith('/login')) {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
// Add security headers
const response = NextResponse.next()
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-Content-Type-Options', 'nosniff')
return response
}
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
```
Access `request` properties in your middleware:
The `middleware.js` file convention is deprecated. Use `proxy.js` instead. The API remains the same—just rename your file from `middleware.ts` to `proxy.ts`.
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/nextjs-middleware-request-interception/raw