content icon indicating copy to clipboard operation
content copied to clipboard

[v3] Live reload only updates the first collection when a file belongs to multiple collections

Open maximepvrt opened this issue 1 year ago • 11 comments

Environment

Reproduction

Code Example

import { defineContentConfig, defineCollection, z } from '@nuxt/content'

export default defineContentConfig({
    collections: {
        content: defineCollection({
            type: 'page',
            source: '**',
            schema: z.object({
                name: z.string()
            })
        }),
        devenirBenevole: defineCollection({
            type: 'page',
            source: 'devenir-benevole/**/*.yml',
        }),
        blogEngagement: defineCollection({
            type: 'page',
            source: 'blog-engagement/*.md',
            schema: z.object({
                date: z.string()
            })
        }),
    }
})

Describe the bug

When modifying a file that belongs to multiple collections, only the first collection in defineContentConfig is updated during live reload. This creates issues when trying to manage a global navigation by combining multiple collections.

Expected Behavior

  1. All collections referencing the modified file should update during live reload.

Additional context

Proposal

A built-in function (e.g., useGlobalNavigation()) to return a global navigation combining all collections would be very helpful, eliminating the need for custom merging. A function like useGlobalNavigation() could return a unified structure of pages with essential metadata (title, path, etc.) from all collections:

const navigation = useGlobalNavigation()

Logs


maximepvrt avatar Jan 13 '25 15:01 maximepvrt

This is expected. Content module preview and HMR handle one-to-one relationships between contents and collections. Also, it is possible to include content in multiple collections, but it is not a best practice in the current state.

We can talk about the needs and requirements of this feature.

farnabaz avatar Jan 14 '25 13:01 farnabaz

The idea is that collections are separate entities, like (blog posts and document pages or landing pages). Having a global navigation utility sounds nice to have, but it will have a big impact on module's database management and performance. Specially in serverless environments and cold start.

What is the usage for the unified navigation?

farnabaz avatar Jan 14 '25 13:01 farnabaz

Thank you for the clarification!

I understand that handling one-to-one relationships between content and collections is the expected behavior. However, I want to explain my use case to clarify why I had to create a "catch-all" collection, even though it might not be considered a best practice.

Why I Use a Catch-All Collection

To build breadcrumbs and manage my site's navigation efficiently, I rely on Nuxt UI v3. Instead of sharing individual navigations for each collection, I centralized all content into a single "global" collection. This way, I can inject and use a unified navigation in my app like this:

const navigation = inject<Ref<ContentNavigationItem[]>>('navigation', ref([]))

Concerns About Best Practices

If having a document in multiple collections is not recommended, wouldn't it be helpful to add at least a warning or even block this behavior?
For example:

  1. A warning could inform users when a document matches multiple collections, suggesting that this might not be ideal.
  2. Alternatively, the module could prevent adding an item to a collection if a prior regex already matches it. In this case, defining an order of precedence for collections in the configuration would make sense.

Why Unified Navigation Would Help

A built-in utility for unified navigation would simplify this process significantly. Instead of relying on workarounds like my "catch-all" collection, developers could directly retrieve a global navigation structure, improving efficiency and clarity.

Let me know if this approach makes sense or if you'd like more details about my use case!

maximepvrt avatar Jan 14 '25 14:01 maximepvrt

What is the usage for the unified navigation?

This is not about global navigation, but it would be nice to have a catch-all collection for building common UI blocks across all collections, e.g. I could add this to App.vue for SEO purposes:

const page = queryCollection('*').path(route.path).first()
const title = computed(() => page.value?.navigation?.title || page.value?.title)

useSeoMeta({
  titleTemplate: '%s - Nuxt Content v3',
  title: title.value,
  ogTitle: `${title.value} - Nuxt Content v3`,
  description: page.value?.description,
  ogDescription: page.value?.description,
})

but it will have a big impact on module's database management and performance.

Not sure how it is implemented, but maybe all collections should use the same database and then queryCollection("foo") will just use where collection = 'foo'.

vmihailenco avatar Jan 15 '25 11:01 vmihailenco

After further reflection, I think it would be better to enforce that a document can belong to only one collection. Allowing a document to be in multiple collections could create issues, especially with tools like Nuxt Studio, which might not know which schema to use when generating the visual editor for such documents.

To address this, a document should be assigned to the first collection whose regex matches it, based on the order of definitions in the configuration. This would ensure consistency and eliminate ambiguity.

As for a unified navigation utility, it could simply be implemented as a composable that calls queryCollectionNavigation for each collection and merges the results. This approach would avoid adding complexity to the module's internal database management while still offering the convenience of a global navigation.

Let me know what you think about this approach!

maximepvrt avatar Jan 15 '25 11:01 maximepvrt

Chiming in, Just had the same issue. My use case is pretty simple:

A manual containing different kinds of documents, like tasks, remarks, vendors. All documents share a field called code which is unique throughout all documents. Authors need a quick way to reference other documents by code and have it displayed to the end-user as document title, code and linking to the proper document. This needs of course to happen between documents of different kinds (or collections, with different schemas), like a vendor can be part of the document of a task.

So in the end, I have thought having manual/tasks/**/*.md, manual/vendors/**/*.md with extended schemas and a simple manual/**/*.md that just sources code frontmatter and the defaults like title and path would solve everything. I load that whole code collection into a pinia store, so in the end I can offer the document authors a simple shortcut like :to{code=VA1} that renders the human readable document title, code and links to the correct target.

This is expected. Content module preview and HMR handle one-to-one relationships between contents and collections.

Did I miss a mention that collections and documents are expected to be 1:1? Why is that important?

My first understanding of the documentation was that collections are based (primarily) on schema definition, where documents are sourced from given globs and offered towards the dev as a collection to query against, no longer depending on the files itself.

As for a unified navigation utility, it could simply be implemented as a composable that calls queryCollectionNavigation for each collection and merges the results.

Thats what I do, I reduce the stem of documents to reduce its depth (I think it is based on /?) in the content:file:afterParse hook for each collection that is not the "code" one. Then I query them within a Promise.all and concat the results together in the transform option of useAsyncData, while watching the locale.

After further reflection, I think it would be better to enforce that a document can belong to only one collection.

As mentioned above, it's not that hard to have a distinction between a document and its presence in different collections, because collections differ in schema definition and that in itself can be useful.

As methods like queryCollectionNavigation are already damn strict by only allowing a single collection, there is also no risk of involving collections that do not matter to me, and I do not require them for the "code" collection at all. Having a collection extracting data from documents is enough for that use-case.

On the other side, if you decide to lock collections down to unique files (as in an path moves from an unparsed stack to a collection stack), a hard error would be appreciated, because otherwise content will get lost in this 1:1 relationship. Also the naming (and even the 1:1 relation) would irk me.

adrianrudnik avatar Feb 03 '25 20:02 adrianrudnik

I am having the same issue. After a few hours of debugging I realised that HMR breaks if queryCollection returns something belonging to multiple collections. That’s a shame since having a global reusable component that returns for instance a FAQ entry for a given path is a useful scenario.

At the very least the docs should make it very clear that setting several collections that resolve to a common source breaks HMR.

chrisjansky avatar Feb 06 '25 11:02 chrisjansky

@chrisjansky I added a warning in the define collection doc

maximepvrt avatar Feb 20 '25 11:02 maximepvrt

So if we're talking about localization, does that mean we should create a separate collection for each language? Is that how it works?🤪

6get-xiaofan avatar May 22 '25 23:05 6get-xiaofan

@6get-xiaofan I'm still figuring out my own stuff, though I would recommend it. As soon as the client runs a query on a collection, it's elevated to a WASM localStorage SQLite thingy. I suspect that, because I've opted to separate languages by a meta property instead of by collection, every visitor to my site would download all German and English content right now.

adrianrudnik avatar May 23 '25 14:05 adrianrudnik

I ran into similar issues with authorization. So allow access to certain content only, basically one needs to structure it on the collection level because you cant go more fine grained..

oripka avatar May 23 '25 14:05 oripka