Skip to content

fix(preview): skip collections without source in preview template#3771

Merged
farnabaz merged 3 commits into
nuxt:mainfrom
humanbird:fix/preview-template-info-collection-source
Apr 23, 2026
Merged

fix(preview): skip collections without source in preview template#3771
farnabaz merged 3 commits into
nuxt:mainfrom
humanbird:fix/preview-template-info-collection-source

Conversation

@humanbird
Copy link
Copy Markdown
Contributor

🔗 Linked issue

Resolves #3767 (and unblocks nuxt-content/nuxt-studio#425)

❓ Type of change

  • 🐞 Bug fix (a non-breaking change that fixes an issue)

📚 Description

The internal info collection (created by resolveCollections() in src/utils/collection.ts) has source: undefined. When previewTemplate serialized this collection it produced source: [] in the preview manifest:

// before
source: collection.source?.filter(source => source.repository ? undefined : collection.source) || [],

undefined?.filter(...)undefined, then the || [] fallback kicks in, so the info collection ends up with an empty source array — which violates the ResolvedCollectionSource[] contract (at least one source is expected) and crashes downstream consumers that read source[0].include and source[0].prefix (most visibly nuxt-studio, see nuxt-content/nuxt-studio#425).

This PR skips source-less collections entirely in the preview manifest, since they cannot be edited from the preview/studio UI anyway. It also simplifies the remote-collection filter to a straightforward !source.repository predicate (the previous ? undefined : collection.source pattern was hard to read and the branch that returned collection.source was only truthy by accident).

Before / After

For a Nuxt 4 project with @nuxt/content v3 plus any user-defined collection, /__preview.json previously contained:

{
  "info": { "source": [], "type": "data", ... },
  "pages": { "source": [{ "include": "**", "prefix": "/", ... }], ... }
}

After this PR the info key is omitted entirely. User-visible collections are unaffected.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly. (no docs change needed — internal behavior only)

The internal `info` collection (created by `resolveCollections()`) has
`source: undefined`. The previous `collection.source?.filter(...) || []`
fallback produced `source: []` in the serialized preview payload, which
violates the `ResolvedCollectionSource[]` contract and crashes
downstream consumers (nuxt-studio) that access `source[0].include` and
`source[0].prefix`.

The `info` collection is internal and cannot be edited from the
preview/studio UI, so it should not appear in the preview manifest
at all. This skips such collections entirely and also simplifies the
remote-collection filter to a straightforward `!source.repository`
predicate.

Resolves nuxt#3767
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 17, 2026

@humanbird is attempting to deploy a commit to the Nuxt Team on Vercel.

A member of the Team first needs to authorize it.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 17, 2026

npm i https://pkg.pr.new/@nuxt/content@3771

commit: 2f7254e

humanbird added a commit to humanbird/nuxt-studio that referenced this pull request Apr 17, 2026
`@nuxt/content` v3 exposes an internal `info` collection whose `source`
is serialized as an empty array (`source: []`) in the preview manifest.
Studio iterates every collection via `parseSourceBase`,
`generateIdFromFsPath`, and `generateFsPathFromId`, which all assumed at
least one `ResolvedCollectionSource` entry and crashed with
`TypeError: Cannot read properties of undefined (reading 'include' |
'prefix')` as soon as the editor tried to list content.

The upstream payload fix is in nuxt/content#3771, but the guards here
are still the correct shape defensively — Studio should tolerate any
future source-less collection without corrupting the editor.

- `parseSourceBase` now short-circuits to empty `fixed`/`dynamic` when
  the source (or its `include`) is missing.
- `getCollectionSourceById` skips sources without `include` during
  matching instead of throwing.
- `generateIdFromFsPath` falls back to `join(name, path)` when the
  collection has no source.
- `generateFsPathFromId` returns the raw path when no source is given.

Resolves nuxt-content#425
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 02735597-b9ea-4f31-a276-a7fffec78613

📥 Commits

Reviewing files that changed from the base of the PR and between cba6554 and 2f7254e.

📒 Files selected for processing (1)
  • src/utils/templates.ts

📝 Walkthrough

Walkthrough

The previewTemplate function in src/utils/templates.ts was modified to change how collections metadata is built. It now computes localSources by filtering collection.source for entries where repository is falsy (collection.source?.filter(source => !source.repository) ?? []). If localSources is empty, that collection is omitted from the accumulated collectionsMeta. For included collections, the source field is set directly to localSources.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: skipping collections without sources in the preview template, which is the core fix addressing the bug.
Description check ✅ Passed The description is thorough and directly related to the changeset, explaining the bug, the fix, the before/after behavior, and linking to the resolved issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils/templates.ts`:
- Around line 227-228: The current mapping that sets "source:
collection.source.filter(source => !source.repository)" can produce an empty
array for fully remote collections, recreating an invalid CollectionInfo.source
shape; update the logic where collections are transformed (the mapping that
assigns source) to first compute filteredSources = collection.source.filter(s =>
!s.repository) and then skip/omit the entire collection if
filteredSources.length === 0 (or do not set CollectionInfo.source) so
remote-only collections are not emitted with source: [] — adjust the transform
that creates CollectionInfo/source accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9bb8414c-f24c-4ec4-a2d6-a754096e59e7

📥 Commits

Reviewing files that changed from the base of the PR and between 22e4085 and 800533e.

📒 Files selected for processing (1)
  • src/utils/templates.ts

Comment thread src/utils/templates.ts Outdated
CodeRabbit flagged that filtering out remote sources could still produce
`source: []` when a collection's sources are *all* remote. Compute the
filtered local sources first and skip the collection entirely if nothing
remains, so the invariant "if a collection appears in the preview
manifest, it has at least one local source" holds for both the no-source
case (e.g. the internal `info` collection) and the remote-only case.
@humanbird
Copy link
Copy Markdown
Contributor Author

Good catch by @coderabbitai — pushed cba6554 to also cover the remote-only case. The logic now computes localSources = collection.source?.filter(s => !s.repository) ?? [] up-front and skips the collection entirely when that's empty, so the invariant "a collection in the preview manifest has ≥ 1 local source" holds for both:

  1. No source at all (internal info collection).
  2. Sources exist but are all remote repositories.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the diff of the latest commit cba6554
git show cba6554c --stat
echo "---"
git show cba6554c

Length of output: 3413


@humanbird: The updated fix in cba6554 looks great! The refactoring to compute localSources up-front and use it as the single guard is a clean improvement over the previous two-step approach. It now correctly handles both edge cases in one place:

  1. No source (collection.source is undefined) → ?? [] yields an empty array → skipped.
  2. All-remote sources.filter(source => !source.repository) yields an empty array → skipped.

The reuse of localSources directly in the metadata object is also a nice bonus — eliminates the now-redundant second .filter() call from the original code. The invariant is cleanly enforced. ✅


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Copy link
Copy Markdown
Collaborator

@larbish larbish left a comment

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

@farnabaz farnabaz left a comment

Choose a reason for hiding this comment

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

👍

@farnabaz farnabaz merged commit 8aabefd into nuxt:main Apr 23, 2026
5 of 6 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.

previewTemplate serializes internal info collection's source: undefined as source: []

3 participants