--- title: 将静态 I/O 提升至模块级别 impact: HIGH impactDescription: 避免每次请求重复执行文件/网络 I/O tags: server, io, performance, next.js, route-handlers, og-image --- ## 将静态 I/O 提升至模块级别 **影响:高(避免每次请求重复执行文件/网络 I/O)** 在路由处理函数或服务端函数中加载静态资源(字体、Logo、图片、配置文件)时,请将 I/O 操作提升至模块级别。模块级别的代码仅在模块首次导入时执行一次,而非每次请求都执行。这可以消除本应在每次调用时反复执行的文件系统读取或网络请求,从而避免冗余开销。 **错误示例(每次请求都读取字体文件):** ```typescript // app/api/og/route.tsx import { ImageResponse } from 'next/og' export async function GET(request: Request) { // 每次请求都会执行——代价高昂! const fontData = await fetch( new URL('./fonts/Inter.ttf', import.meta.url) ).then(res => res.arrayBuffer()) const logoData = await fetch( new URL('./images/logo.png', import.meta.url) ).then(res => res.arrayBuffer()) return new ImageResponse(
Hello World
, { fonts: [{ name: 'Inter', data: fontData }] } ) } ``` **正确示例(在模块初始化时加载一次):** ```typescript // app/api/og/route.tsx import { ImageResponse } from 'next/og' // 模块级别:仅在模块首次导入时执行一次 const fontData = fetch( new URL('./fonts/Inter.ttf', import.meta.url) ).then(res => res.arrayBuffer()) const logoData = fetch( new URL('./images/logo.png', import.meta.url) ).then(res => res.arrayBuffer()) export async function GET(request: Request) { // 等待已开始执行的 Promise const [font, logo] = await Promise.all([fontData, logoData]) return new ImageResponse(
Hello World
, { fonts: [{ name: 'Inter', data: font }] } ) } ``` **正确示例(在模块级别使用同步 fs):** ```typescript // app/api/og/route.tsx import { ImageResponse } from 'next/og' import { readFileSync } from 'fs' import { join } from 'path' // 模块级别的同步读取——仅在模块初始化时阻塞 const fontData = readFileSync( join(process.cwd(), 'public/fonts/Inter.ttf') ) const logoData = readFileSync( join(process.cwd(), 'public/images/logo.png') ) export async function GET(request: Request) { return new ImageResponse(
Hello World
, { fonts: [{ name: 'Inter', data: fontData }] } ) } ``` **错误示例(每次调用都读取配置):** ```typescript import fs from 'node:fs/promises' export async function processRequest(data: Data) { const config = JSON.parse( await fs.readFile('./config.json', 'utf-8') ) const template = await fs.readFile('./template.html', 'utf-8') return render(template, data, config) } ``` **正确示例(将配置和模板提升至模块级别):** ```typescript import fs from 'node:fs/promises' const configPromise = fs .readFile('./config.json', 'utf-8') .then(JSON.parse) const templatePromise = fs.readFile('./template.html', 'utf-8') export async function processRequest(data: Data) { const [config, template] = await Promise.all([ configPromise, templatePromise, ]) return render(template, data, config) } 何时使用此模式: - 为 OG 图片生成加载字体 - 加载静态 Logo、图标或水印 - 读取运行时不发生变化的配置文件 - 加载邮件模板或其他静态模板 - 在所有请求中保持不变的任何静态资源 何时不使用此模式: - 因请求或用户而变化的资源 - 运行期间可能发生更改的文件(应改用带 TTL 的缓存) - 如果一直加载会消耗过多内存的大文件 - 不应在内存中持久保存的敏感数据 借助 Vercel 的 [Fluid Compute](https://vercel.com/docs/fluid-compute),模块级缓存在多个并发请求共享同一函数实例时尤为有效。静态资源在请求之间保持加载在内存中,不会产生冷启动开销。 在传统 Serverless 环境中,每次冷启动都会重新执行模块级代码,但随后的热调用会复用已加载的资源,直至实例被回收。