Skip to content

Conversation

@aaharu
Copy link

@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

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

coderabbitai bot commented Jan 9, 2026

πŸ“ 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 gzip, brotli, and zstd pre-compression, change default compression wording, and expand compressible MIME types list.
Compression utility
src/utils/compress.ts
Add EncodingMap including .zst; detect zstd support (zlib.zstdCompress); allow zstd in enabled encodings; skip existing .zst files; implement 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 type and map .zst ↔ zstd encoding so pre-compressed .zst assets are included in asset metadata and served based on Accept-Encoding.
Tests / Snapshots
test/presets/cloudflare-pages.test.ts
Update snapshot to include excluded route entries for .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 docstrings
πŸ§ͺ 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

@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.

fileContents,
{
params: {
[zlib.constants.ZSTD_c_compressionLevel]: 19,
Copy link
Member

Choose a reason for hiding this comment

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

It sees too much CPU consumption. For JS assets, 3-5 might be enough

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
[zlib.constants.ZSTD_c_compressionLevel]: 19,
[zlib.constants.ZSTD_c_compressionLevel]: 4,

Copy link
Author

Choose a reason for hiding this comment

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

I agree, but the documentation stated The best compression level is used., so I was doing it this way. I will consider changing the documentation as well.

Copy link
Member

Choose a reason for hiding this comment

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

Default is 3 (most balanced), i think we can leave it as is and reword docs as default/balanced

@pkg-pr-new
Copy link

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
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