Edge Runtime 和 Node.js Runtime 有什么区别?如何为页面、路由或中间件选择运行时?

50 阅读3分钟

Edge Runtime 和 Node.js Runtime 有什么区别?如何为页面、路由或中间件选择运行时?

Edge Runtime vs Node.js Runtime

1. 基本概念

Node.js Runtime

  • 完整的 Node.js 环境
  • 支持所有 Node.js API
  • 更大的内存和计算资源
  • 更长的执行时间

Edge Runtime

  • 基于 Web APIs 的轻量级运行时
  • 更快的冷启动
  • 更小的内存占用
  • 更短的执行时间

2. 功能对比

// Node.js Runtime - 支持所有 Node.js API
// app/api/node-route/route.js
export async function GET() {
  // ✅ 支持文件系统操作
  const fs = await import('fs/promises')
  const data = await fs.readFile('file.txt', 'utf8')

  // ✅ 支持子进程
  const { spawn } = await import('child_process')

  // ✅ 支持所有 npm 包
  const bcrypt = await import('bcrypt')

  return NextResponse.json({ data })
}

// Edge Runtime - 只支持 Web APIs
// app/api/edge-route/route.js
export const runtime = 'edge'

export async function GET() {
  // ✅ 支持 Web APIs
  const response = await fetch('https://api.example.com')
  const data = await response.json()

  // ✅ 支持 URL 和 Headers
  const url = new URL(request.url)
  const headers = new Headers()

  // ❌ 不支持文件系统
  // const fs = await import('fs/promises') // 错误

  // ❌ 不支持子进程
  // const { spawn } = await import('child_process') // 错误

  return NextResponse.json({ data })
}

运行时选择

1. 页面级别配置

// app/edge-page/page.js
export const runtime = 'edge'

export default function EdgePage() {
  return (
    <div>
      <h1>Edge Runtime Page</h1>
      <p>This page runs on Edge Runtime</p>
    </div>
  )
}

2. API 路由配置

// app/api/edge-api/route.js
export const runtime = 'edge'

export async function GET(request) {
  const { searchParams } = new URL(request.url)
  const query = searchParams.get('q')

  // 使用 Web APIs
  const response = await fetch(`https://api.example.com/search?q=${query}`)
  const data = await response.json()

  return NextResponse.json(data)
}

3. 中间件配置

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

export function middleware(request) {
  // 中间件默认运行在 Edge Runtime
  const url = request.nextUrl.clone()

  // 重定向逻辑
  if (url.pathname === '/old-path') {
    url.pathname = '/new-path'
    return NextResponse.redirect(url)
  }

  // 添加自定义头部
  const response = NextResponse.next()
  response.headers.set('x-custom-header', 'value')

  return response
}

实际应用场景

1. Edge Runtime 适用场景

// app/api/geolocation/route.js
export const runtime = 'edge'

export async function GET(request) {
  const { searchParams } = new URL(request.url)
  const lat = searchParams.get('lat')
  const lng = searchParams.get('lng')

  // 地理位置处理
  const response = await fetch(
    `https://api.geoapify.com/v1/geocode/reverse?lat=${lat}&lon=${lng}&apiKey=${process.env.GEO_API_KEY}`
  )

  const data = await response.json()

  return NextResponse.json({
    location: data.features[0]?.properties,
  })
}
// app/api/weather/route.js
export const runtime = 'edge'

export async function GET(request) {
  const { searchParams } = new URL(request.url)
  const city = searchParams.get('city')

  // 天气 API 调用
  const response = await fetch(
    `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${process.env.WEATHER_API_KEY}`
  )

  const data = await response.json()

  return NextResponse.json({
    temperature: data.main.temp,
    description: data.weather[0].description,
  })
}

2. Node.js Runtime 适用场景

// app/api/file-upload/route.js
export const runtime = 'nodejs'

export async function POST(request) {
  const formData = await request.formData()
  const file = formData.get('file')

  // 文件系统操作
  const fs = await import('fs/promises')
  const path = await import('path')

  const buffer = await file.arrayBuffer()
  const filename = `${Date.now()}-${file.name}`
  const filepath = path.join(process.cwd(), 'uploads', filename)

  await fs.writeFile(filepath, Buffer.from(buffer))

  return NextResponse.json({ filename })
}
// app/api/database/route.js
export const runtime = 'nodejs'

export async function GET() {
  // 数据库操作
  const { PrismaClient } = await import('@prisma/client')
  const prisma = new PrismaClient()

  const users = await prisma.user.findMany()

  return NextResponse.json({ users })
}

性能对比

1. 冷启动时间

// Edge Runtime - 更快的冷启动
// app/api/fast-api/route.js
export const runtime = 'edge'

export async function GET() {
  // 冷启动时间: ~50ms
  const start = Date.now()

  const response = await fetch('https://api.example.com')
  const data = await response.json()

  const duration = Date.now() - start

  return NextResponse.json({
    data,
    duration: `${duration}ms`,
  })
}

// Node.js Runtime - 较慢的冷启动
// app/api/slow-api/route.js
export const runtime = 'nodejs'

export async function GET() {
  // 冷启动时间: ~200ms
  const start = Date.now()

  const fs = await import('fs/promises')
  const data = await fs.readFile('data.json', 'utf8')

  const duration = Date.now() - start

  return NextResponse.json({
    data: JSON.parse(data),
    duration: `${duration}ms`,
  })
}

2. 内存使用

// Edge Runtime - 更少的内存使用
// app/api/lightweight/route.js
export const runtime = 'edge'

export async function GET() {
  // 内存使用: ~10MB
  const data = {
    timestamp: Date.now(),
    random: Math.random(),
  }

  return NextResponse.json(data)
}

// Node.js Runtime - 更多的内存使用
// app/api/heavyweight/route.js
export const runtime = 'nodejs'

export async function GET() {
  // 内存使用: ~50MB
  const fs = await import('fs/promises')
  const path = await import('path')
  const crypto = await import('crypto')

  const data = {
    timestamp: Date.now(),
    hash: crypto.createHash('sha256').update('data').digest('hex'),
  }

  return NextResponse.json(data)
}

最佳实践

1. 运行时选择指南

// 选择 Edge Runtime 的情况
export const runtime = 'edge'

// ✅ 简单的 API 调用
// ✅ 地理位置处理
// ✅ 重定向和头部操作
// ✅ 轻量级数据处理
// ✅ 需要快速响应的场景

// 选择 Node.js Runtime 的情况
export const runtime = 'nodejs'

// ✅ 文件系统操作
// ✅ 数据库操作
// ✅ 复杂的计算
// ✅ 使用 Node.js 特定的包
// ✅ 需要长时间运行的任务

2. 混合使用

// app/api/hybrid/route.js
export const runtime = 'edge'

export async function GET(request) {
  // 在 Edge Runtime 中处理简单逻辑
  const { searchParams } = new URL(request.url)
  const query = searchParams.get('q')

  if (!query) {
    return NextResponse.json({ error: 'Query required' }, { status: 400 })
  }

  // 调用 Node.js API 进行复杂处理
  const response = await fetch(`${process.env.NODE_API_URL}/process`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query }),
  })

  const data = await response.json()

  return NextResponse.json(data)
}

3. 错误处理

// app/api/edge-with-error-handling/route.js
export const runtime = 'edge'

export async function GET(request) {
  try {
    const { searchParams } = new URL(request.url)
    const query = searchParams.get('q')

    if (!query) {
      return NextResponse.json(
        { error: 'Query parameter is required' },
        { status: 400 }
      )
    }

    const response = await fetch(`https://api.example.com/search?q=${query}`)

    if (!response.ok) {
      return NextResponse.json(
        { error: 'External API error' },
        { status: response.status }
      )
    }

    const data = await response.json()
    return NextResponse.json(data)
  } catch (error) {
    console.error('Edge Runtime error:', error)
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}

总结

运行时选择要点:

Edge Runtime

  • 更快的冷启动
  • 更少的内存使用
  • 只支持 Web APIs
  • 适合简单、快速的操作

Node.js Runtime

  • 支持所有 Node.js API
  • 更多的内存和计算资源
  • 适合复杂的操作
  • 支持文件系统和数据库

选择指南

  • 简单 API 调用 → Edge Runtime
  • 文件系统操作 → Node.js Runtime
  • 数据库操作 → Node.js Runtime
  • 地理位置处理 → Edge Runtime
  • 重定向和头部操作 → Edge Runtime

最佳实践

  • 根据功能需求选择运行时
  • 考虑性能要求
  • 处理错误情况
  • 监控运行时性能