Skip to content

feat: support zstd for compressPublicAssets#3934

Merged
pi0 merged 8 commits intonitrojs:mainfrom
aaharu:public-assets-zstd
Feb 6, 2026
Merged

feat: support zstd for compressPublicAssets#3934
pi0 merged 8 commits intonitrojs:mainfrom
aaharu:public-assets-zstd

Conversation

@aaharu
Copy link
Copy Markdown
Contributor

@aaharu aaharu commented Jan 9, 2026

🔗 Linked issue

resolves #2772

❓ Type of change

  • 📖 Documentation (updates to the documentation, readme, or JSdoc annotations)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

Added zstd support to compressPublicAssets with zlib.zstdCompress.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@aaharu aaharu requested a review from pi0 as a code owner January 9, 2026 10:45
@vercel
Copy link
Copy Markdown

vercel Bot commented Jan 9, 2026

@aaharu is attempting to deploy a commit to the Nitro Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 9, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds Zstandard (zstd) support across config, docs, compression tooling, public-asset metadata/serving, and tests so .zst pre-compressed files are produced, detected, and served alongside gzip and brotli.

Changes

Cohort / File(s) Summary
Configuration & Documentation
docs/3.config/0.index.md, src/types/config.ts
Add zstd?: boolean to CompressOptions; update docs to mention pre-compression for gzip, brotli, and zstd, revise default compression wording, and expand the list of compressible MIME types.
Compression utility
src/utils/compress.ts
Introduce EncodingMap with .zst; detect runtime zstd support (zlib.zstdCompress); support enabling/disabling zstd via config; skip existing .zst files; add zstd compression path and integrate with existing gzip/brotli flow and suffix logic.
Asset metadata & serving
src/build/virtual/public-assets.ts, src/runtime/internal/static.ts
Recognize .zst suffix when deriving MIME types; map .zstzstd encoding so .zst assets are included in asset metadata and served based on Accept-Encoding.
Tests / Snapshots
test/presets/cloudflare-pages.test.ts
Update snapshot exclude list to include .zst variants (e.g. /_openapi.json.zst, /_scalar/index.html.zst, /_swagger/index.html.zst, /prerender/index.html.zst).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows conventional commits format with 'feat:' prefix and clearly describes the addition of zstd support to compressPublicAssets.
Description check ✅ Passed The PR description is directly related to the changeset, explaining the addition of zstd support using zlib.zstdCompress and linking to issue #2772.
Linked Issues check ✅ Passed The code changes fully implement the zstd compression support objective from issue #2772, including type definitions, compression logic, encoding mappings, and test updates.
Out of Scope Changes check ✅ Passed All changes directly support zstd compression implementation: config docs, type definitions, compression utility logic, encoding mappings, and test snapshot updates are all in scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/utils/compress.ts (2)

21-33: Hoist invariant computations outside the loop.

The configuration parsing (lines 21-32) and zstdSupported check (line 33) produce the same values for every file but are computed inside the per-file callback. Consider hoisting these before Promise.all to avoid redundant work.

♻️ Suggested refactor
 export async function compressPublicAssets(nitro: Nitro) {
+  const compressPublicAssets = nitro.options.compressPublicAssets;
+  if (compressPublicAssets === false) {
+    return;
+  }
+
+  const {
+    gzip = false,
+    brotli = false,
+    zstd = false,
+  } = compressPublicAssets === true
+    ? { gzip: true, brotli: true, zstd: true }
+    : compressPublicAssets;
+  const zstdSupported = zlib.zstdCompress !== undefined;
+
   const publicFiles = await glob("**", {
     cwd: nitro.options.output.publicDir,
     absolute: false,
     dot: true,
     ignore: ["**/*.gz", "**/*.br", "**/*.zst"],
   });

   await Promise.all(
     publicFiles.map(async (fileName) => {
-      const compressPublicAssets = nitro.options.compressPublicAssets;
-      if (compressPublicAssets === false) {
-        return;
-      }
-
-      const {
-        gzip = false,
-        brotli = false,
-        zstd = false,
-      } = compressPublicAssets === true
-        ? { gzip: true, brotli: true, zstd: true }
-        : compressPublicAssets;
-      const zstdSupported = zlib.zstdCompress !== undefined;
       const filePath = resolve(nitro.options.output.publicDir, fileName);

81-97: Use explicit encoding check for zstd branch.

Line 85 uses else if (zstdSupported) instead of else if (encoding === "zstd"). While the encodings array filters out zstd when unsupported, this condition:

  1. Is less clear about intent than an explicit encoding check
  2. Rechecks a condition already verified at line 58
  3. Could theoretically leave the Promise unresolved if zstdSupported is false
♻️ Suggested fix
               if (encoding === "gzip") {
                 zlib.gzip(fileContents, gzipOptions, cb);
               } else if (encoding === "br") {
                 zlib.brotliCompress(fileContents, brotliOptions, cb);
-              } else if (zstdSupported) {
+              } else {
+                // encoding === "zstd" (zstd support verified in encodings filter)
                 zlib.zstdCompress(
                   fileContents,
                   {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99691fc and 8af0bd3.

📒 Files selected for processing (6)
  • docs/3.config/0.index.md
  • src/build/virtual/public-assets.ts
  • src/runtime/internal/static.ts
  • src/types/config.ts
  • src/utils/compress.ts
  • test/presets/cloudflare-pages.test.ts
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.{ts,js,tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{ts,js,tsx,jsx}: Use pathe for cross-platform path operations instead of Node.js node:path
Use ESM and modern JavaScript
Do not add comments explaining what the line does unless prompted

Files:

  • src/runtime/internal/static.ts
  • src/build/virtual/public-assets.ts
  • src/types/config.ts
  • src/utils/compress.ts
src/**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

Use unstorage for storage abstraction

Files:

  • src/runtime/internal/static.ts
  • src/build/virtual/public-assets.ts
  • src/types/config.ts
  • src/utils/compress.ts
src/runtime/**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

src/runtime/**/*.{ts,js}: Runtime code in src/runtime/ must be runtime-agnostic: do not use Node.js-specific APIs unless behind runtime checks
In runtime code, prefer Web APIs (fetch, Request, Response, URL, etc.) over Node.js APIs
In runtime code, use only console for logging (no consola)
Keep runtime code minimal and side-effect free to reduce bundle size

Files:

  • src/runtime/internal/static.ts
src/{build,dev,runner,cli}/**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

Use consola for logging in build/dev code; use nitro.logger when available

Files:

  • src/build/virtual/public-assets.ts
src/build/**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

Virtual modules must be registered in src/build/virtual.ts

Files:

  • src/build/virtual/public-assets.ts
🧠 Learnings (1)
📚 Learning: 2025-12-24T11:45:17.435Z
Learnt from: CR
Repo: nitrojs/nitro PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-24T11:45:17.435Z
Learning: Applies to src/runtime/**/*.{ts,js} : Keep runtime code minimal and side-effect free to reduce bundle size

Applied to files:

  • src/runtime/internal/static.ts
🧬 Code graph analysis (1)
src/utils/compress.ts (1)
src/types/nitro.ts (1)
  • Nitro (16-37)
🔇 Additional comments (14)
src/types/config.ts (1)

350-354: LGTM! Type definition correctly extends compression options.

The zstd option follows the same pattern as gzip and brotli, maintaining consistency in the API design.

src/build/virtual/public-assets.ts (2)

39-39: LGTM! MIME type detection correctly handles zstd extension.

The regex now strips .zst alongside .gz and .br to determine the correct MIME type of compressed assets.


61-63: LGTM! Encoding detection follows established pattern.

The zstd encoding assignment correctly mirrors the existing logic for gzip and brotli compressed assets.

test/presets/cloudflare-pages.test.ts (1)

47-47: LGTM! Test snapshot correctly updated for zstd variants.

The exclusion list now includes .zst compressed variants alongside existing .gz and .br files, ensuring Cloudflare Pages routes are correctly configured for all compression formats.

Also applies to: 58-58, 61-61, 66-66

docs/3.config/0.index.md (2)

176-180: LGTM! Documentation accurately reflects zstd support.

The default configuration correctly shows zstd: false for backward compatibility, and the description clearly explains that zstd is now supported alongside gzip and brotli for pre-compression of assets larger than 1024 bytes.


181-231: LGTM! Comprehensive list of compressible MIME types.

The documented MIME type list provides clear guidance on which asset types will be compressed. This is helpful for users to understand what will be affected when enabling compression.

src/runtime/internal/static.ts (1)

18-18: LGTM! Runtime encoding map extended appropriately.

The addition of zstd: ".zst" follows the existing pattern and keeps the runtime code minimal and side-effect free, adhering to coding guidelines. The src/utils/compress.ts utility properly handles zstd support through capability detection—checking if zlib.zstdCompress is defined at runtime rather than assuming a specific Node.js version, with appropriate guards around all zstd operations.

src/utils/compress.ts (7)

1-7: LGTM!

Imports are appropriate and follow the coding guidelines - using pathe for path operations and ESM imports throughout.


9-10: LGTM!

Clean encoding-to-suffix mapping with proper as const assertion for type safety.


36-42: Verify: Early return prevents adding new encoding formats to pre-compressed files.

The OR logic causes the function to skip a file entirely if any enabled compression format already exists. This means:

  • If a user has existing .gz files and later enables zstd, no .zst files will be generated for those assets.
  • The per-encoding check at lines 65-67 would handle this correctly, making this early return an optimization that changes behavior.

Is this intentional? If users are expected to regenerate all compressed assets when adding a new format, consider documenting this behavior. Otherwise, consider changing to AND logic or removing this early check.


44-53: LGTM!

Appropriate criteria for skipping compression: minimum file size, source maps, and non-compressible MIME types.


55-59: LGTM!

Clean encodings array construction with proper type guard. The zstd && zstdSupported check ensures zstd is only included when both configured and available.


107-166: LGTM!

Helper functions are unchanged and correctly identify compressible MIME types. The comprehensive MIME type list from AWS CloudFront documentation is appropriate.


86-96: The runtime check at line 33 (zlib.zstdCompress !== undefined) is already in place and properly guards all usage of zstdCompress throughout the file, gracefully handling Node.js versions < 22.15.0 where the API is unavailable. No action needed.

Comment thread src/utils/compress.ts Outdated
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jan 9, 2026

Open in StackBlitz

npm i https://pkg.pr.new/nitrojs/nitro@3934

commit: df3777b

@aaharu aaharu requested a review from pi0 January 27, 2026 15:12
Copy link
Copy Markdown
Member

@pi0 pi0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@pi0 pi0 merged commit 8deb70e into nitrojs:main Feb 6, 2026
6 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Zstandard as compression algorithm

2 participants