Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Apply 62 React and Next.js performance optimization rules from Vercel Engineering
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
rules/server-hoist-static-io.md
1---2title: Hoist Static I/O to Module Level3impact: HIGH4impactDescription: avoids repeated file/network I/O per request5tags: server, io, performance, next.js, route-handlers, og-image6---78## Hoist Static I/O to Module Level910**Impact: HIGH (avoids repeated file/network I/O per request)**1112When loading static assets (fonts, logos, images, config files) in route handlers or server functions, hoist the I/O operation to module level. Module-level code runs once when the module is first imported, not on every request. This eliminates redundant file system reads or network fetches that would otherwise run on every invocation.1314**Incorrect (reads font file on every request):**1516```typescript17// app/api/og/route.tsx18import { ImageResponse } from 'next/og'1920export async function GET(request: Request) {21// Runs on EVERY request - expensive!22const fontData = await fetch(23new URL('./fonts/Inter.ttf', import.meta.url)24).then(res => res.arrayBuffer())2526const logoData = await fetch(27new URL('./images/logo.png', import.meta.url)28).then(res => res.arrayBuffer())2930return new ImageResponse(31<div style={{ fontFamily: 'Inter' }}>32<img src={logoData} />33Hello World34</div>,35{ fonts: [{ name: 'Inter', data: fontData }] }36)37}38```3940**Correct (loads once at module initialization):**4142```typescript43// app/api/og/route.tsx44import { ImageResponse } from 'next/og'4546// Module-level: runs ONCE when module is first imported47const fontData = fetch(48new URL('./fonts/Inter.ttf', import.meta.url)49).then(res => res.arrayBuffer())5051const logoData = fetch(52new URL('./images/logo.png', import.meta.url)53).then(res => res.arrayBuffer())5455export async function GET(request: Request) {56// Await the already-started promises57const [font, logo] = await Promise.all([fontData, logoData])5859return new ImageResponse(60<div style={{ fontFamily: 'Inter' }}>61<img src={logo} />62Hello World63</div>,64{ fonts: [{ name: 'Inter', data: font }] }65)66}67```6869**Correct (synchronous fs at module level):**7071```typescript72// app/api/og/route.tsx73import { ImageResponse } from 'next/og'74import { readFileSync } from 'fs'75import { join } from 'path'7677// Synchronous read at module level - blocks only during module init78const fontData = readFileSync(79join(process.cwd(), 'public/fonts/Inter.ttf')80)8182const logoData = readFileSync(83join(process.cwd(), 'public/images/logo.png')84)8586export async function GET(request: Request) {87return new ImageResponse(88<div style={{ fontFamily: 'Inter' }}>89<img src={logoData} />90Hello World91</div>,92{ fonts: [{ name: 'Inter', data: fontData }] }93)94}95```9697**Incorrect (reads config on every call):**9899```typescript100import fs from 'node:fs/promises'101102export async function processRequest(data: Data) {103const config = JSON.parse(104await fs.readFile('./config.json', 'utf-8')105)106const template = await fs.readFile('./template.html', 'utf-8')107108return render(template, data, config)109}110```111112**Correct (hoists config and template to module level):**113114```typescript115import fs from 'node:fs/promises'116117const configPromise = fs118.readFile('./config.json', 'utf-8')119.then(JSON.parse)120const templatePromise = fs.readFile('./template.html', 'utf-8')121122export async function processRequest(data: Data) {123const [config, template] = await Promise.all([124configPromise,125templatePromise,126])127128return render(template, data, config)129}130```131132When to use this pattern:133134- Loading fonts for OG image generation135- Loading static logos, icons, or watermarks136- Reading configuration files that don't change at runtime137- Loading email templates or other static templates138- Any static asset that's the same across all requests139140When not to use this pattern:141142- Assets that vary per request or user143- Files that may change during runtime (use caching with TTL instead)144- Large files that would consume too much memory if kept loaded145- Sensitive data that shouldn't persist in memory146147With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute), module-level caching is especially effective because multiple concurrent requests share the same function instance. The static assets stay loaded in memory across requests without cold start penalties.148149In traditional serverless, each cold start re-executes module-level code, but subsequent warm invocations reuse the loaded assets until the instance is recycled.150