Next.js: API Routes 与 Route Handlers

最后更新:2026-08-26

Route Handlers 是 Next.js 的 API 层——当 Server Actions 不够用时,标准 RESTful 端点仍然是现代 web 的基石。

1. 你将学到


2. 一个 DevOps 工程师的真实故事

(1) 痛点:团队在 5 个页面中重复实现了 8 次认证逻辑

Diana 在审核 TaskFlow 代码时发现:每个 API Route 的开头都是同样的 15 行认证代码——从 cookie 解析 session、验证 token、返回 401。8 个 API 端点 × 15 行 = 120 行重复代码。更糟的是,有 3 个端点忘了加认证检查,直接把用户数据暴露给了未登录请求。

问题 数据
重复认证代码 120 行(8 端点 × 15 行)
未保护的端点 3 个
安全审计失败 2 次
修复时间 每次 4 小时

(2) Middleware 的解法

middleware.ts 统一处理认证、日志、CORS——一个文件控制所有请求的生命周期。

TS
// middleware.ts — 统一认证 + 日志
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const token = request.cookies.get('session-token')?.value

  // 检查 API 请求的认证
  if (request.nextUrl.pathname.startsWith('/api/')) {
    if (!token) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
    }
  }

  // 添加安全头
  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/:path*',
}

(3) 收益

维度 前(无 middleware) 后(middleware)
认证代码 120 行(分散) 15 行(集中)
未保护端点 3 个 0 个
安全审计 ❌ 失败 ✅ 通过
新增端点工作量 15 行认证 + 逻辑 仅逻辑

3. Route Handlers 基础

Route Handlers 通过 app/api/**/route.ts 文件定义,每个 HTTP 方法对应一个导出的异步函数:

HTTP 方法 导出函数 路由文件
GET export async function GET() app/api/items/route.ts
POST export async function POST() app/api/items/route.ts
PUT export async function PUT() app/api/items/[id]/route.ts
DELETE export async function DELETE() app/api/items/[id]/route.ts
PATCH export async function PATCH() app/api/items/[id]/route.ts

(1) 基本 GET 端点

TS
// app/api/items/route.ts
import { NextResponse } from 'next/server'

export async function GET() {
  const items = await db.item.findMany()
  return NextResponse.json(items)
}

(2) 动态路由参数

TS
// app/api/items/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const item = await db.item.findUnique({ where: { id: Number(params.id) } })
  if (!item) {
    return NextResponse.json({ error: 'Item not found' }, { status: 404 })
  }
  return NextResponse.json(item)
}

(3) POST 创建资源

TS
// app/api/items/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  const body = await request.json()
  const item = await db.item.create({ data: body })
  return NextResponse.json(item, { status: 201 })
}

▶ 示例:CRUD 完整 Route Handler(难度⭐⭐)

TS
// app/api/todos/route.ts — GET + POST
import { NextRequest, NextResponse } from 'next/server'

const API = 'https://jsonplaceholder.typicode.com'

export async function GET() {
  const todos = await fetch(`${API}/todos?_limit=5`).then(r => r.json())
  return NextResponse.json(todos)
}

export async function POST(request: NextRequest) {
  const body = await request.json()
  const todo = await fetch(`${API}/todos`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  }).then(r => r.json())
  return NextResponse.json(todo, { status: 201 })
}
TS
// app/api/todos/[id]/route.ts — GET + PUT + DELETE
import { NextRequest, NextResponse } from 'next/server'

const API = 'https://jsonplaceholder.typicode.com'

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const todo = await fetch(`${API}/todos/${params.id}`).then(r => r.json())
  if (!todo || todo.id === undefined) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 })
  }
  return NextResponse.json(todo)
}

export async function PUT(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const body = await request.json()
  const updated = await fetch(`${API}/todos/${params.id}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  }).then(r => r.json())
  return NextResponse.json(updated)
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  await fetch(`${API}/todos/${params.id}`, { method: 'DELETE' })
  return NextResponse.json({ success: true })
}

4. Zod 请求校验

REST API 的请求数据校验同样推荐使用 Zod——比手动 if 检查更简洁、类型安全。

(1) 请求体校验

TS
// app/api/items/route.ts — Zod 校验 POST 请求体
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

const createItemSchema = z.object({
  name: z.string().min(2).max(100),
  price: z.number().positive('Price must be positive'),
  category: z.enum(['electronics', 'clothing', 'food']),
  tags: z.array(z.string()).max(5).optional(),
})

export async function POST(request: NextRequest) {
  const body = await request.json()
  const validated = createItemSchema.safeParse(body)

  if (!validated.success) {
    return NextResponse.json(
      { error: 'Validation failed', details: validated.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  const item = await db.item.create({ data: validated.data })
  return NextResponse.json(item, { status: 201 })
}

(2) 查询参数校验

TS
// app/api/items/route.ts — Zod 校验查询参数
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

const querySchema = z.object({
  page: z.coerce.number().int().positive().default(1),
  limit: z.coerce.number().int().min(1).max(100).default(10),
  search: z.string().optional(),
})

export async function GET(request: NextRequest) {
  const { searchParams } = request.nextUrl
  const query = querySchema.safeParse({
    page: searchParams.get('page'),
    limit: searchParams.get('limit'),
    search: searchParams.get('search'),
  })

  if (!query.success) {
    return NextResponse.json(
      { error: 'Invalid query parameters', details: query.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  const { page, limit, search } = query.data
  const items = await db.item.findMany({
    skip: (page - 1) * limit,
    take: limit,
    where: search ? { name: { contains: search } } : undefined,
  })
  return NextResponse.json({ items, page, limit })
}

▶ 示例:Zod 校验完整 API(难度⭐⭐)

TS
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { revalidateTag } from 'next/cache'

const userSchema = z.object({
  name: z.string().min(2, 'Name too short'),
  email: z.string().email('Invalid email'),
  role: z.enum(['admin', 'user', 'viewer']).default('user'),
})

export async function POST(request: NextRequest) {
  const body = await request.json()
  const validated = userSchema.safeParse(body)

  if (!validated.success) {
    return NextResponse.json(
      { error: 'Validation failed', fields: validated.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  const user = await fetch('https://jsonplaceholder.typicode.com/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(validated.data),
  }).then(r => r.json())

  revalidateTag('users')
  return NextResponse.json(user, { status: 201 })
}

5. NextResponse 响应格式

NextResponse 是 Next.js 提供的专用响应工具,支持多种响应格式:

方法 用途 示例
NextResponse.json(data, opts?) JSON 响应 NextResponse.json({ id: 1 }, { status: 201 })
NextResponse.redirect(url) 重定向 NextResponse.redirect(new URL('/login', request.url))
NextResponse.next() 继续中间件链 return NextResponse.next()
NextResponse.rewrite(url) 服务端 URL 重写 NextResponse.rewrite(new URL('/fallback', request.url))

(1) JSON 响应

TS
// app/api/auth/route.ts
import { NextResponse } from 'next/server'

export async function GET() {
  return NextResponse.json({ status: 'healthy', uptime: process.uptime() })
}

(2) 重定向

TS
// app/api/redirect/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  const target = request.nextUrl.searchParams.get('to') ?? '/'
  return NextResponse.redirect(new URL(target, request.url))
}

(3) Rewrite(URL 重写)

TS
// middleware.ts — 将 /products/old-slug 重写到新路径
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/products/old-')) {
    const newPath = request.nextUrl.pathname.replace('/old-', '/')
    return NextResponse.rewrite(new URL(newPath, request.url))
  }
  return NextResponse.next()
}

▶ 示例:响应格式对比(难度⭐)

TS
// app/api/response-demo/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  const format = request.nextUrl.searchParams.get('format') ?? 'json'

  switch (format) {
    case 'json':
      return NextResponse.json({ message: 'Hello', timestamp: Date.now() })
    case 'redirect':
      return NextResponse.redirect(new URL('/api/response-demo?format=json', request.url))
    case 'rewrite':
      return NextResponse.rewrite(new URL('/api/response-demo?format=json', request.url))
    default:
      return NextResponse.json({ error: 'Unknown format' }, { status: 400 })
  }
}

6. Middleware 中间件

middleware.ts 是 Next.js 的请求拦截器——在每个请求到达页面或 API 之前执行,支持路径匹配、请求改写、Header 注入、认证检查。

100%
sequenceDiagram
    participant Client as Browser
    participant MW as middleware.ts
    participant Route as Route Handler/Page

    Client->>MW: 请求 /api/todos
    MW->>MW: 匹配 matcher 规则
    MW->>MW: 认证检查 / Header 注入
    alt 通过
        MW->>Route: NextResponse.next()
        Route-->>Client: 正常响应
    else 未认证
        MW-->>Client: NextResponse.json(401)
    else 重定向
        MW-->>Client: NextResponse.redirect(/login)
    end
配置 类型 说明
matcher string[] 匹配路径模式(支持 glob)
request.nextUrl URL 当前请求的 URL 对象
request.cookies Map 请求 cookies
request.headers Headers 请求头
NextResponse.next() Response 继续正常处理
NextResponse.redirect() Response 重定向到其他 URL

(1) matcher 配置

TS
// middleware.ts — matcher 配置
export const config = {
  matcher: [
    '/api/:path*',           // 所有 API 路由
    '/dashboard/:path*',     // 所有 dashboard 页面
    '/((?!_next|static|favicon.ico).*)',  // 排除静态资源
  ],
}

(2) 常见 Middleware 模式

TS
// middleware.ts — 常见模式汇总
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl

  // 1. 认证检查
  const token = request.cookies.get('session')?.value
  const isAuthPage = pathname.startsWith('/login') || pathname.startsWith('/register')

  if (!token && !isAuthPage && pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  // 2. 安全头
  const response = NextResponse.next()
  response.headers.set('X-Frame-Options', 'DENY')
  response.headers.set('X-Content-Type-Options', 'nosniff')
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')

  // 3. 添加 request ID(追踪)
  const requestId = crypto.randomUUID()
  response.headers.set('X-Request-Id', requestId)

  return response
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/:path*'],
}

▶ 示例:Middleware 日志记录(难度⭐)

TS
// middleware.ts — 请求日志
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const start = Date.now()
  const { method, nextUrl } = request

  console.log(`[${new Date().toISOString()}] ${method} ${nextUrl.pathname}`)

  const response = NextResponse.next()

  // 异步记录响应时间
  response.headers.set('X-Response-Time', `${Date.now() - start}ms`)

  return response
}

export const config = {
  matcher: '/api/:path*',
}

7. CORS 与 Rate Limiting

(1) CORS 配置

TS
// app/api/cors-config/route.ts — 单个端点的 CORS
import { NextRequest, NextResponse } from 'next/server'

const corsHeaders = {
  'Access-Control-Allow-Origin': 'https://your-app.com',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
  'Access-Control-Max-Age': '86400',
}

export async function OPTIONS() {
  return NextResponse.json({}, { headers: corsHeaders })
}

export async function GET() {
  return NextResponse.json(
    { data: 'CORS enabled' },
    { headers: corsHeaders }
  )
}

(2) 全局 CORS Middleware

TS
// middleware.ts — 全局 CORS
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/api/')) {
    const response = NextResponse.next()
    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')

    // 预检请求直接返回
    if (request.method === 'OPTIONS') {
      return new Response(null, { status: 204, headers: response.headers })
    }

    return response
  }
  return NextResponse.next()
}

export const config = {
  matcher: '/api/:path*',
}

(3) Rate Limiting

TS
// lib/rate-limit.ts — 简单的内存 Rate Limiter
const rateMap = new Map<string, { count: number; resetAt: number }>()

export function rateLimit(key: string, maxRequests: number, windowMs: number): boolean {
  const now = Date.now()
  const record = rateMap.get(key)

  if (!record || now > record.resetAt) {
    rateMap.set(key, { count: 1, resetAt: now + windowMs })
    return true  // 允许
  }

  if (record.count >= maxRequests) {
    return false  // 限制
  }

  record.count++
  return true
}
TS
// app/api/rate-limited/route.ts — 使用 Rate Limiter
import { NextRequest, NextResponse } from 'next/server'
import { rateLimit } from '@/lib/rate-limit'

export async function GET(request: NextRequest) {
  const ip = request.headers.get('x-forwarded-for') ?? 'anonymous'
  const allowed = rateLimit(ip, 10, 60_000)  // 每分钟 10 次

  if (!allowed) {
    return NextResponse.json(
      { error: 'Too many requests' },
      { status: 429, headers: { 'Retry-After': '60' } }
    )
  }

  return NextResponse.json({ message: 'Success', timestamp: Date.now() })
}

▶ 示例:完整 CORS + Rate Limiting(难度⭐⭐⭐)

TS
// app/api/secure/route.ts — CORS + Rate Limit + Auth
import { NextRequest, NextResponse } from 'next/server'
import { rateLimit } from '@/lib/rate-limit'
import { z } from 'zod'

const postSchema = z.object({
  title: z.string().min(2).max(200),
  content: z.string().min(10),
})

export async function POST(request: NextRequest) {
  // 1. Rate Limit
  const ip = request.headers.get('x-forwarded-for') ?? 'unknown'
  if (!rateLimit(ip, 20, 60_000)) {
    return NextResponse.json({ error: 'Rate limit exceeded' }, { status: 429 })
  }

  // 2. Auth check
  const auth = request.headers.get('authorization')
  if (!auth?.startsWith('Bearer ') || auth.slice(7) !== process.env.API_KEY) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  // 3. Validation
  const body = await request.json()
  const validated = postSchema.safeParse(body)
  if (!validated.success) {
    return NextResponse.json(
      { error: 'Validation failed', details: validated.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  // 4. Process
  const post = await fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    body: JSON.stringify({ ...validated.data, userId: 1 }),
  }).then(r => r.json())

  return NextResponse.json(post, { status: 201, headers: {
    'Access-Control-Allow-Origin': '*',
  }})
}

export async function OPTIONS() {
  return NextResponse.json({}, { headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'POST, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
  }})
}

8. 完整示例:RESTful API 服务

TS
// app/api/posts/route.ts — Posts CRUD API
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { revalidateTag } from 'next/cache'

// ======== Schemas ========
const createPostSchema = z.object({
  title: z.string().min(2).max(200),
  body: z.string().min(10).max(5000),
  userId: z.number().int().positive(),
})

const querySchema = z.object({
  page: z.coerce.number().int().positive().default(1),
  limit: z.coerce.number().int().min(1).max(50).default(10),
})

const API = 'https://jsonplaceholder.typicode.com'

// ======== GET /api/posts?page=1&limit=10 ========
export async function GET(request: NextRequest) {
  const query = querySchema.safeParse({
    page: request.nextUrl.searchParams.get('page'),
    limit: request.nextUrl.searchParams.get('limit'),
  })

  if (!query.success) {
    return NextResponse.json(
      { error: 'Invalid query', details: query.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  const { page, limit } = query.data
  const posts = await fetch(`${API}/posts?_page=${page}&_limit=${limit}`).then(r => r.json())
  const total = 100  // JSONPlaceholder 总量

  return NextResponse.json({
    data: posts,
    pagination: { page, limit, total, totalPages: Math.ceil(total / limit) }
  })
}

// ======== POST /api/posts ========
export async function POST(request: NextRequest) {
  const body = await request.json()
  const validated = createPostSchema.safeParse(body)

  if (!validated.success) {
    return NextResponse.json(
      { error: 'Validation failed', fields: validated.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  const post = await fetch(`${API}/posts`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(validated.data),
  }).then(r => r.json())

  revalidateTag('posts')
  return NextResponse.json(post, { status: 201 })
}
TS
// app/api/posts/[id]/route.ts — 单文章 CRUD
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

const updatePostSchema = z.object({
  title: z.string().min(2).max(200).optional(),
  body: z.string().min(10).max(5000).optional(),
})

const API = 'https://jsonplaceholder.typicode.com'

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const post = await fetch(`${API}/posts/${params.id}`).then(r => {
    if (!r.ok) throw new Error('Not found')
    return r.json()
  }).catch(() => null)

  if (!post) {
    return NextResponse.json({ error: 'Post not found' }, { status: 404 })
  }
  return NextResponse.json(post)
}

export async function PATCH(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const body = await request.json()
  const validated = updatePostSchema.safeParse(body)

  if (!validated.success) {
    return NextResponse.json(
      { error: 'Validation failed', fields: validated.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  const updated = await fetch(`${API}/posts/${params.id}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(validated.data),
  }).then(r => r.json())

  return NextResponse.json(updated)
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  await fetch(`${API}/posts/${params.id}`, { method: 'DELETE' })
  return NextResponse.json({ success: true, id: params.id })
}
TS
// middleware.ts — 全局 API 中间件
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { rateLimit } from '@/lib/rate-limit'

export function middleware(request: NextRequest) {
  const { pathname, origin } = request.nextUrl

  if (!pathname.startsWith('/api/')) {
    return NextResponse.next()
  }

  // 1. Rate Limiting
  const ip = request.headers.get('x-forwarded-for') ?? 'unknown'
  if (!rateLimit(ip, 30, 60_000)) {
    return NextResponse.json({ error: 'Too many requests' }, {
      status: 429,
      headers: { 'Retry-After': '60' }
    })
  }

  // 2. Logging
  console.log(`[API] ${request.method} ${pathname}`)

  // 3. CORS
  const response = NextResponse.next()
  response.headers.set('Access-Control-Allow-Origin', '*')
  response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
  response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key')

  if (request.method === 'OPTIONS') {
    return new Response(null, { status: 204, headers: response.headers })
  }

  return response
}

export const config = {
  matcher: '/api/:path*',
}

❓ 常见问题

Q Route Handler 和 Server Action 有什么区别?
A Route Handler 是标准的 HTTP 端点,通过 route.ts 定义,适合第三方 API 集成、Webhook、移动端调用。Server Action 是 RPC 风格的服务端函数,通过 'use server' 定义,适合第一方表单操作。区别在于:Route Handler 需要 HTTP 调用,Server Action 是函数调用;Route Handler 无 CSRF 保护,Server Action 内置。
Q NextResponse.json 和直接 new Response() 有什么区别?
A NextResponse.json 是 NextResponse 的便捷方法,自动设置 Content-Type: application/json 并提供更好的 TypeScript 类型推断。new Response(JSON.stringify(data), { headers: {'Content-Type': 'application/json'} }) 是等价的。推荐使用 NextResponse.json 以保持代码简洁。
Q middleware.ts 可以读取数据库吗?
A 可以,但需要注意性能——middleware 在每个匹配的请求中执行。数据库查询会增加延迟。推荐只在 middleware 中做轻量操作(cookie 解析、header 检查、重定向)。数据库操作应放在 Route Handler 或 Server Action 中。
Q Route Handler 支持 Edge Runtime 吗?
A 支持。Route Handler 默认运行在 Node.js Runtime,但可以通过 export const runtime = 'edge' 切换到 Edge Runtime。Edge Runtime 延迟更低,但 API 受限(不支持 Node.js 原生模块如 fs、crypto)。Edge Runtime 适合简单代理、认证校验、A/B 测试。
Q 如何保护 API 端点不被滥用?
A 三层防护:① Middleware 层做 IP-based Rate Limiting;② Route Handler 层做 API Key / JWT 认证;③ 全局 CORS 限制允许的来源。生产环境建议使用 Redis-backed Rate Limiter(如 upstash/ratelimit)以支持多实例共享状态。
Q Route Handler 中如何获取客户端 IP?
A 通过 request.headers 获取:request.headers.get('x-forwarded-for')?.split(',')[0]?.trim()request.headers.get('x-real-ip')。Vercel 部署时会自动添加这些头。本地开发时可能返回 ::1127.0.0.1

📖 小节


📝 作业

  1. 基础题(⭐):创建 app/api/health/route.ts 返回 JSON 健康检查信息(状态、时间戳、uptime)。添加 middleware 记录每次 API 调用的日志和响应时间。

  2. 进阶题(⭐⭐):构建一个完整的 CRUD API:app/api/books/route.ts(GET 列表 + POST 创建)+ app/api/books/[id]/route.ts(GET 详情 + PUT 更新 + DELETE 删除)。使用 Zod 校验请求体。在 middleware 中添加 API Key 认证。

  3. 挑战题(⭐⭐⭐):实现一个"短链接服务"API:app/api/shorten/route.ts(POST 接受 url 返回短码)、app/api/[code]/route.ts(GET 读取短码并 302 重定向)。添加 Rate Limiting(每分钟 10 个短链接/IP)、CORS 跨域支持、Middleware 访问日志。短码存储使用 JSON 文件或内存 Map。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

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

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