Skip to content

Conversation

@farnabaz
Copy link
Member

@farnabaz farnabaz commented Nov 18, 2025

… config hook

🔗 Linked issue

resolves #3613

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 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)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

This was an attempt to solve #3613. But it seems that it works only if we move route to /api/**.

Nitro cache + server handler on modules:done hook does not work if it is not prefixed with /api /cc @pi0 @atinux

📝 Checklist

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

@vercel
Copy link

vercel bot commented Nov 18, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
content Error Error Dec 5, 2025 5:08pm

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Nov 18, 2025

Deploying content with  Cloudflare Pages  Cloudflare Pages

Latest commit: fa6efdd
Status: ✅  Deploy successful!
Preview URL: https://a38d9af4.content-f0q.pages.dev
Branch Preview URL: https://fix-nitro-cache.content-f0q.pages.dev

View logs

// Due to prerender enabling in the module, Nuxt create a route for each collection
// These routes cause issue while enabling cache in Nuxt.
// So we need to add a server handler for each collection to handle the request.
manifest.collections.map(async (collection) => {
Copy link

Choose a reason for hiding this comment

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

Using .map(async ...) without awaiting creates unnecessary promises that are discarded, and should use .forEach() instead for synchronous operations.

View Details
📝 Patch Details
diff --git a/src/presets/cloudflare.ts b/src/presets/cloudflare.ts
index 501b30ba..ad0b4bf7 100644
--- a/src/presets/cloudflare.ts
+++ b/src/presets/cloudflare.ts
@@ -7,7 +7,7 @@ import { collectionDumpTemplate } from '../utils/templates'
 export default definePreset({
   name: 'cloudflare',
   setup(_options, _nuxt, { resolver, manifest }) {
-    manifest.collections.map(async (collection) => {
+    manifest.collections.forEach((collection) => {
       addServerHandler({
         route: `/__nuxt_content/${collection.name}/sql_dump.txt`,
         handler: resolver.resolve('./runtime/presets/cloudflare/database-handler'),
@@ -25,7 +25,7 @@ export default definePreset({
     nitroConfig.handlers ||= []
 
     // Add raw content dump
-    manifest.collections.map(async (collection) => {
+    manifest.collections.forEach((collection) => {
       if (!collection.private) {
         addTemplate(collectionDumpTemplate(collection.name, manifest))
       }
diff --git a/src/presets/node.ts b/src/presets/node.ts
index 4b4bb340..f805dc24 100644
--- a/src/presets/node.ts
+++ b/src/presets/node.ts
@@ -8,7 +8,7 @@ export default definePreset({
     // Due to prerender enabling in the module, Nuxt create a route for each collection
     // These routes cause issue while enabling cache in Nuxt.
     // So we need to add a server handler for each collection to handle the request.
-    manifest.collections.map(async (collection) => {
+    manifest.collections.forEach((collection) => {
       addServerHandler({
         route: `/__nuxt_content/${collection.name}/sql_dump.txt`,
         handler: resolver.resolve('./runtime/presets/node/database-handler'),

Analysis

Unnecessary async callbacks in preset collection loops create discarded promises

What fails: src/presets/node.ts (line 11) and src/presets/cloudflare.ts (lines 10, 28) use .map(async (collection) => { addServerHandler(...) }) without awaiting the returned promise array, creating unnecessary promise objects that are immediately garbage collected.

How to reproduce: The code works functionally but creates and discards promises:

// Current pattern (creates unused promises)
const result = manifest.collections.map(async (collection) => {
  addServerHandler({ route: `...` })
})
// result is Array<Promise<undefined>> - these promises are never resolved/awaited

Result: Arrays of unused Promise objects are created and garbage collected, with no functional benefit. The async keyword is semantically incorrect since addServerHandler() is synchronous (returns void).

Expected behavior: Use .forEach() for side effects without return values, matching the pattern in src/utils/templates.ts line 52 which correctly uses await Promise.all() when async operations need awaiting, and the established codebase pattern where synchronous calls use .forEach().

Reference: Per Nuxt Kit documentation, addServerHandler() signature is function addServerHandler (handler: NitroEventHandler): void - synchronous with no async behavior.

Fix: Replace .map(async (collection) => {...}) with .forEach((collection) => {...}) to eliminate unnecessary promise creation and clarify intent.


// Add raw content dump to public assets
nitroConfig.publicAssets.push({ dir: join(nitroConfig.buildDir!, 'content', 'raw'), maxAge: 60 })
nitroConfig.handlers.push({
Copy link

Choose a reason for hiding this comment

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

Using .map(async ...) without awaiting creates unnecessary promises that are discarded, and should use .forEach() instead for synchronous operations.

View Details
📝 Patch Details
diff --git a/src/presets/cloudflare.ts b/src/presets/cloudflare.ts
index 501b30ba..ad0b4bf7 100644
--- a/src/presets/cloudflare.ts
+++ b/src/presets/cloudflare.ts
@@ -7,7 +7,7 @@ import { collectionDumpTemplate } from '../utils/templates'
 export default definePreset({
   name: 'cloudflare',
   setup(_options, _nuxt, { resolver, manifest }) {
-    manifest.collections.map(async (collection) => {
+    manifest.collections.forEach((collection) => {
       addServerHandler({
         route: `/__nuxt_content/${collection.name}/sql_dump.txt`,
         handler: resolver.resolve('./runtime/presets/cloudflare/database-handler'),
@@ -25,7 +25,7 @@ export default definePreset({
     nitroConfig.handlers ||= []
 
     // Add raw content dump
-    manifest.collections.map(async (collection) => {
+    manifest.collections.forEach((collection) => {
       if (!collection.private) {
         addTemplate(collectionDumpTemplate(collection.name, manifest))
       }
diff --git a/src/presets/node.ts b/src/presets/node.ts
index 4b4bb340..f805dc24 100644
--- a/src/presets/node.ts
+++ b/src/presets/node.ts
@@ -8,7 +8,7 @@ export default definePreset({
     // Due to prerender enabling in the module, Nuxt create a route for each collection
     // These routes cause issue while enabling cache in Nuxt.
     // So we need to add a server handler for each collection to handle the request.
-    manifest.collections.map(async (collection) => {
+    manifest.collections.forEach((collection) => {
       addServerHandler({
         route: `/__nuxt_content/${collection.name}/sql_dump.txt`,
         handler: resolver.resolve('./runtime/presets/node/database-handler'),

Analysis

Unhandled promise rejections from unnecessary async callbacks in preset setup methods

What fails: setupNitro() in src/presets/cloudflare.ts (lines 10, 28) and src/presets/node.ts (line 11) use .map(async ...) without awaiting the returned promises. Since the callbacks (addServerHandler and addTemplate) are synchronous, this pattern creates unnecessary Promise objects and can result in unhandled promise rejections if any error occurs inside the async callback.

How to reproduce:

// In src/presets/cloudflare.ts setupNitro method, if any error occurs:
manifest.collections.map(async (collection) => {
  if (!collection.private) {
    addTemplate(collectionDumpTemplate(collection.name, manifest))
    // If an error is thrown here, it becomes an unhandled promise rejection
    // because the returned promises are never awaited
  }
})

Result: Any errors thrown inside the async callback become unhandled promise rejections. These are silently lost unless there's a global unhandled rejection handler. The code still functions for the synchronous operations, but error handling is broken.

Expected: Since addServerHandler() and addTemplate() from @nuxt/kit are synchronous functions, the async wrapper is unnecessary and creates unhandled rejection scenarios. Using .forEach() eliminates the promise wrapping and ensures errors propagate normally.

Fix applied: Changed .map(async (collection) => {...}) to .forEach((collection) => {...}) in:

  • src/presets/cloudflare.ts line 10 (setup method)
  • src/presets/cloudflare.ts line 28 (setupNitro method)
  • src/presets/node.ts line 11 (setup method)

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.

Nuxt Content API crashes when using Nitro multi-domain cache (varies)

2 participants