Next.js Route Handlers #

一、Route Handlers概述 #

1.1 什么是Route Handlers #

Route Handlers是App Router中的API路由,使用Web标准Request和Response对象:

tsx
import { NextResponse } from 'next/server'

export async function GET(request: Request) {
    return NextResponse.json({ message: 'Hello' })
}

1.2 与Pages Router的区别 #

特性 App Router Pages Router
文件 route.ts pages/api/*.ts
请求对象 Request NextApiRequest
响应对象 Response NextApiResponse
运行时 Node.js/Edge Node.js

二、流式响应 #

2.1 基本流式响应 #

tsx
import { NextResponse } from 'next/server'

export async function GET() {
    const encoder = new TextEncoder()
    
    const stream = new ReadableStream({
        async start(controller) {
            const messages = ['Hello', ' ', 'World', '!']
            
            for (const message of messages) {
                controller.enqueue(encoder.encode(message))
                await new Promise(resolve => setTimeout(resolve, 100))
            }
            
            controller.close()
        },
    })
    
    return new NextResponse(stream, {
        headers: {
            'Content-Type': 'text/plain',
            'Transfer-Encoding': 'chunked',
        },
    })
}

2.2 Server-Sent Events #

tsx
import { NextResponse } from 'next/server'

export async function GET() {
    const encoder = new TextEncoder()
    
    const stream = new ReadableStream({
        async start(controller) {
            let count = 0
            
            const interval = setInterval(() => {
                const data = JSON.stringify({ time: new Date(), count: count++ })
                controller.enqueue(encoder.encode(`data: ${data}\n\n`))
            }, 1000)
            
            setTimeout(() => {
                clearInterval(interval)
                controller.close()
            }, 30000)
        },
    })
    
    return new NextResponse(stream, {
        headers: {
            'Content-Type': 'text/event-stream',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
        },
    })
}

2.3 AI流式响应 #

tsx
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
    const { prompt } = await request.json()
    
    const encoder = new TextEncoder()
    
    const stream = new ReadableStream({
        async start(controller) {
            const response = await fetch('https://api.openai.com/v1/chat/completions', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
                },
                body: JSON.stringify({
                    model: 'gpt-4',
                    messages: [{ role: 'user', content: prompt }],
                    stream: true,
                }),
            })
            
            const reader = response.body?.getReader()
            
            if (reader) {
                while (true) {
                    const { done, value } = await reader.read()
                    if (done) break
                    controller.enqueue(value)
                }
            }
            
            controller.close()
        },
    })
    
    return new NextResponse(stream, {
        headers: {
            'Content-Type': 'text/event-stream',
        },
    })
}

三、文件处理 #

3.1 文件上传 #

tsx
import { NextRequest, NextResponse } from 'next/server'
import { writeFile } from 'fs/promises'
import path from 'path'

export async function POST(request: NextRequest) {
    const formData = await request.formData()
    const file = formData.get('file') as File
    
    if (!file) {
        return NextResponse.json({ error: 'No file uploaded' }, { status: 400 })
    }
    
    const bytes = await file.arrayBuffer()
    const buffer = Buffer.from(bytes)
    
    const filename = `${Date.now()}-${file.name}`
    const filepath = path.join(process.cwd(), 'public', 'uploads', filename)
    
    await writeFile(filepath, buffer)
    
    return NextResponse.json({
        success: true,
        filename,
        url: `/uploads/${filename}`,
    })
}

3.2 多文件上传 #

tsx
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
    const formData = await request.formData()
    const files = formData.getAll('files') as File[]
    
    const uploadedFiles = []
    
    for (const file of files) {
        const bytes = await file.arrayBuffer()
        const buffer = Buffer.from(bytes)
        
        const filename = `${Date.now()}-${file.name}`
        await writeFile(`public/uploads/${filename}`, buffer)
        
        uploadedFiles.push({
            filename,
            originalName: file.name,
            size: file.size,
        })
    }
    
    return NextResponse.json({ files: uploadedFiles })
}

3.3 文件下载 #

tsx
import { NextResponse } from 'next/server'
import { readFile } from 'fs/promises'
import path from 'path'

export async function GET(request: Request, { params }: { params: Promise<{ filename: string }> }) {
    const { filename } = await params
    const filepath = path.join(process.cwd(), 'public', 'uploads', filename)
    
    try {
        const file = await readFile(filepath)
        
        return new NextResponse(file, {
            headers: {
                'Content-Type': 'application/octet-stream',
                'Content-Disposition': `attachment; filename="${filename}"`,
            },
        })
    } catch {
        return NextResponse.json({ error: 'File not found' }, { status: 404 })
    }
}

四、Edge Runtime #

4.1 配置Edge Runtime #

tsx
export const runtime = 'edge'

export async function GET() {
    return new Response('Hello from Edge!')
}

4.2 Edge Runtime特点 #

特性 Node.js Runtime Edge Runtime
启动时间 较慢 极快
支持API 完整Node.js Web标准API
包大小 无限制 有限制
数据库 支持所有 需要兼容

4.3 Edge示例 #

tsx
export const runtime = 'edge'

export async function GET(request: Request) {
    const country = request.headers.get('x-vercel-ip-country') || 'Unknown'
    
    return new Response(JSON.stringify({ country }), {
        headers: { 'Content-Type': 'application/json' },
    })
}

五、WebSocket #

5.1 升级请求 #

tsx
import { NextRequest } from 'next/server'

export async function GET(request: NextRequest) {
    const upgradeHeader = request.headers.get('upgrade')
    
    if (upgradeHeader !== 'websocket') {
        return new Response('Expected WebSocket', { status: 426 })
    }
    
    return new Response('WebSocket support requires custom server', {
        status: 501,
    })
}

5.2 使用第三方服务 #

tsx
import { NextResponse } from 'next/server'

export async function GET() {
    const wsUrl = process.env.WEBSOCKET_URL
    
    return NextResponse.json({
        websocket: wsUrl,
    })
}

六、配置选项 #

6.1 动态配置 #

tsx
export const dynamic = 'force-dynamic'
export const dynamicParams = true
export const revalidate = 0
export const fetchCache = 'force-no-store'
export const runtime = 'nodejs'

6.2 路由段配置 #

配置 说明
dynamic ‘auto’ 自动决定
dynamic ‘force-dynamic’ 强制动态
runtime ‘nodejs’ Node.js运行时
runtime ‘edge’ Edge运行时

七、最佳实践 #

7.1 错误处理 #

tsx
import { NextResponse } from 'next/server'

export async function GET() {
    try {
        const data = await fetchData()
        return NextResponse.json(data)
    } catch (error) {
        console.error('API Error:', error)
        
        return NextResponse.json(
            { error: 'Internal Server Error' },
            { status: 500 }
        )
    }
}

7.2 验证 #

tsx
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

const schema = z.object({
    name: z.string().min(1),
    email: z.string().email(),
})

export async function POST(request: NextRequest) {
    const body = await request.json()
    const result = schema.safeParse(body)
    
    if (!result.success) {
        return NextResponse.json(
            { error: 'Validation failed', details: result.error.issues },
            { status: 400 }
        )
    }
    
    return NextResponse.json({ success: true })
}

7.3 日志 #

tsx
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
    const start = Date.now()
    
    console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`)
    
    const response = NextResponse.json({ data: 'test' })
    
    console.log(`Completed in ${Date.now() - start}ms`)
    
    return response
}

八、总结 #

Route Handlers要点:

要点 说明
流式响应 ReadableStream
SSE text/event-stream
文件上传 formData
Edge Runtime runtime = ‘edge’
配置选项 dynamic/runtime

下一步,让我们学习Server Actions!

最后更新:2026-03-28