[PR #398] [CLOSED] perf: async static file serving and precompressed hashed assets #543

Closed
opened 2026-05-06 13:08:40 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/398
Author: @NathanDrake2406
Created: 3/10/2026
Status: Closed

Base: mainHead: perf/prod-server-static-serving


📝 Commits (2)

  • aa2d475 perf: async static file serving and precompressed hashed assets
  • 7cf552a test: add tests for precompressAssets and async tryServeStatic

📊 Changes

2 files changed (+249 additions, -9 deletions)

View changed files

📝 packages/vinext/src/server/prod-server.ts (+124 -9)
📝 tests/features.test.ts (+125 -0)

📄 Description

Summary

  • Async stat: Replace synchronous existsSync + statSync in tryServeStatic with fs.promises.stat — stops blocking the event loop on every static file request
  • Precompressed hashed assets: New precompressAssets() runs at startup, brotli (q11) + gzip (l9) compresses all compressible files under assets/ into an in-memory map. Hashed asset requests serve these buffers directly instead of re-compressing per hit
  • Content-Length on uncompressed responses: Uncompressed static file responses now include Content-Length from the stat result

Details

The Node.js prod server was doing two expensive things on every static file request:

  1. Sync filesystem probesexistsSync + statSync block the event loop. Replaced with a single async fsp.stat() that catches ENOENT.

  2. Per-request compression of immutable assets — Files under /assets/ have content hashes and Cache-Control: immutable, yet were re-compressed with brotli/gzip on every request. Now compressed once at startup with max compression settings (brotli q11, gzip l9) since the cost is amortized. The compressed buffers are served directly via res.end(buf) — no streaming compressor needed.

Non-hashed assets (e.g. favicon.ico) still use on-the-fly compression as before.

Test plan

  • precompressAssets compresses JS/CSS above size threshold
  • precompressAssets skips files below threshold
  • precompressAssets returns empty map for missing assets/ directory
  • precompressAssets skips non-compressible types (images)
  • tryServeStatic serves precompressed brotli buffer for hashed assets
  • tryServeStatic returns false for non-existent files via async stat
  • All existing prod-server tests pass (features: 236, pages-router: 133, app-router: 209)
  • CI: format, lint, typecheck, vitest, playwright

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/398 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 3/10/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `perf/prod-server-static-serving` --- ### 📝 Commits (2) - [`aa2d475`](https://github.com/cloudflare/vinext/commit/aa2d475b74a601dcccf93e607d37c364996493e7) perf: async static file serving and precompressed hashed assets - [`7cf552a`](https://github.com/cloudflare/vinext/commit/7cf552aa262aa504a79eddb167d29a7545ca3500) test: add tests for precompressAssets and async tryServeStatic ### 📊 Changes **2 files changed** (+249 additions, -9 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/server/prod-server.ts` (+124 -9) 📝 `tests/features.test.ts` (+125 -0) </details> ### 📄 Description ## Summary - **Async stat**: Replace synchronous `existsSync` + `statSync` in `tryServeStatic` with `fs.promises.stat` — stops blocking the event loop on every static file request - **Precompressed hashed assets**: New `precompressAssets()` runs at startup, brotli (q11) + gzip (l9) compresses all compressible files under `assets/` into an in-memory map. Hashed asset requests serve these buffers directly instead of re-compressing per hit - **Content-Length on uncompressed responses**: Uncompressed static file responses now include `Content-Length` from the stat result ## Details The Node.js prod server was doing two expensive things on every static file request: 1. **Sync filesystem probes** — `existsSync` + `statSync` block the event loop. Replaced with a single async `fsp.stat()` that catches ENOENT. 2. **Per-request compression of immutable assets** — Files under `/assets/` have content hashes and `Cache-Control: immutable`, yet were re-compressed with brotli/gzip on every request. Now compressed once at startup with max compression settings (brotli q11, gzip l9) since the cost is amortized. The compressed buffers are served directly via `res.end(buf)` — no streaming compressor needed. Non-hashed assets (e.g. `favicon.ico`) still use on-the-fly compression as before. ## Test plan - [x] `precompressAssets` compresses JS/CSS above size threshold - [x] `precompressAssets` skips files below threshold - [x] `precompressAssets` returns empty map for missing `assets/` directory - [x] `precompressAssets` skips non-compressible types (images) - [x] `tryServeStatic` serves precompressed brotli buffer for hashed assets - [x] `tryServeStatic` returns false for non-existent files via async stat - [x] All existing prod-server tests pass (features: 236, pages-router: 133, app-router: 209) - [ ] CI: format, lint, typecheck, vitest, playwright --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:08:40 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#543
No description provided.