Files
gh-jezweb-claude-skills-ski…/templates/proxy-migration.ts
2025-11-30 08:25:04 +08:00

275 lines
7.7 KiB
TypeScript

/**
* Next.js 16 - Proxy Migration (middleware.ts → proxy.ts)
*
* BREAKING CHANGE: middleware.ts is deprecated in Next.js 16.
* Use proxy.ts instead.
*
* Migration: Rename file and function, keep same logic.
*/
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// ============================================================================
// Example 1: Basic Proxy (Auth Check)
// ============================================================================
export function proxy(request: NextRequest) {
const token = request.cookies.get('token')
// Redirect to login if no token
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: '/dashboard/:path*',
}
// ============================================================================
// Example 2: Advanced Proxy (Multiple Checks)
// ============================================================================
export function advancedProxy(request: NextRequest) {
const { pathname } = request.nextUrl
// 1. Auth check
const token = request.cookies.get('token')
if (pathname.startsWith('/dashboard') && !token) {
return NextResponse.redirect(new URL('/login', request.url))
}
// 2. Role-based access
const userRole = request.cookies.get('role')?.value
if (pathname.startsWith('/admin') && userRole !== 'admin') {
return NextResponse.redirect(new URL('/unauthorized', request.url))
}
// 3. Add custom headers
const response = NextResponse.next()
response.headers.set('x-custom-header', 'value')
response.headers.set('x-pathname', pathname)
return response
}
export const advancedConfig = {
matcher: ['/dashboard/:path*', '/admin/:path*'],
}
// ============================================================================
// Example 3: Request Rewriting
// ============================================================================
export function rewriteProxy(request: NextRequest) {
// Rewrite /blog/* to /posts/*
if (request.nextUrl.pathname.startsWith('/blog')) {
const url = request.nextUrl.clone()
url.pathname = url.pathname.replace('/blog', '/posts')
return NextResponse.rewrite(url)
}
return NextResponse.next()
}
export const rewriteConfig = {
matcher: '/blog/:path*',
}
// ============================================================================
// Example 4: Geolocation-Based Routing
// ============================================================================
export function geoProxy(request: NextRequest) {
const country = request.geo?.country || 'US'
const url = request.nextUrl.clone()
// Redirect to country-specific page
if (url.pathname === '/') {
url.pathname = `/${country.toLowerCase()}`
return NextResponse.rewrite(url)
}
return NextResponse.next()
}
// ============================================================================
// Example 5: A/B Testing
// ============================================================================
export function abTestProxy(request: NextRequest) {
const bucket = request.cookies.get('bucket')?.value
if (!bucket) {
// Assign to A or B randomly
const newBucket = Math.random() < 0.5 ? 'a' : 'b'
const response = NextResponse.next()
response.cookies.set('bucket', newBucket, {
maxAge: 60 * 60 * 24 * 30, // 30 days
})
// Rewrite to variant page
if (newBucket === 'b') {
const url = request.nextUrl.clone()
url.pathname = `/variant-b${url.pathname}`
return NextResponse.rewrite(url)
}
return response
}
// Existing user
if (bucket === 'b') {
const url = request.nextUrl.clone()
url.pathname = `/variant-b${url.pathname}`
return NextResponse.rewrite(url)
}
return NextResponse.next()
}
export const abTestConfig = {
matcher: '/',
}
// ============================================================================
// Example 6: Rate Limiting
// ============================================================================
const rateLimitMap = new Map<string, { count: number; resetAt: number }>()
export function rateLimitProxy(request: NextRequest) {
const ip = request.headers.get('x-forwarded-for') || 'unknown'
const now = Date.now()
// Check rate limit (100 requests per minute)
const rateLimit = rateLimitMap.get(ip)
if (rateLimit) {
if (now < rateLimit.resetAt) {
if (rateLimit.count >= 100) {
return new NextResponse('Too Many Requests', {
status: 429,
headers: {
'Retry-After': String(Math.ceil((rateLimit.resetAt - now) / 1000)),
},
})
}
rateLimit.count++
} else {
rateLimitMap.set(ip, { count: 1, resetAt: now + 60000 }) // 1 minute
}
} else {
rateLimitMap.set(ip, { count: 1, resetAt: now + 60000 })
}
return NextResponse.next()
}
export const rateLimitConfig = {
matcher: '/api/:path*',
}
// ============================================================================
// Example 7: Response Modification
// ============================================================================
export function modifyResponseProxy(request: NextRequest) {
const response = NextResponse.next()
// Add security headers
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('Referrer-Policy', 'origin-when-cross-origin')
response.headers.set(
'Permissions-Policy',
'camera=(), microphone=(), geolocation=()'
)
return response
}
// ============================================================================
// Migration Guide: middleware.ts → proxy.ts
// ============================================================================
/**
* ❌ BEFORE (Next.js 15):
*
* // File: middleware.ts
* import { NextResponse } from 'next/server'
* import type { NextRequest } from 'next/server'
*
* export function middleware(request: NextRequest) {
* const token = request.cookies.get('token')
* if (!token) {
* return NextResponse.redirect(new URL('/login', request.url))
* }
* return NextResponse.next()
* }
*
* export const config = {
* matcher: '/dashboard/:path*',
* }
*/
/**
* ✅ AFTER (Next.js 16):
*
* // File: proxy.ts
* import { NextResponse } from 'next/server'
* import type { NextRequest } from 'next/server'
*
* export function proxy(request: NextRequest) {
* const token = request.cookies.get('token')
* if (!token) {
* return NextResponse.redirect(new URL('/login', request.url))
* }
* return NextResponse.next()
* }
*
* export const config = {
* matcher: '/dashboard/:path*',
* }
*/
/**
* Migration Steps:
* 1. Rename file: middleware.ts → proxy.ts
* 2. Rename function: middleware → proxy
* 3. Keep config object the same
* 4. Logic remains identical
*
* Why the change?
* - proxy.ts runs on Node.js runtime (full Node.js APIs)
* - middleware.ts ran on Edge runtime (limited APIs)
* - proxy.ts makes the network boundary explicit
*
* Note: middleware.ts still works in Next.js 16 but is deprecated.
* Migrate to proxy.ts for future compatibility.
*/
/**
* Summary:
*
* Proxy patterns:
* 1. ✅ Auth checks and redirects
* 2. ✅ Role-based access control
* 3. ✅ Custom headers
* 4. ✅ Request rewriting (URL rewrites)
* 5. ✅ Geolocation-based routing
* 6. ✅ A/B testing
* 7. ✅ Rate limiting
* 8. ✅ Response modification (security headers)
*
* Best practices:
* - Keep proxy logic lightweight (runs on every request)
* - Use matcher to limit scope
* - Avoid database queries (use cookies/headers instead)
* - Cache rate limit data in memory (or Redis for production)
* - Return NextResponse.next() if no action needed
*/