Getting Started

Migration

How to migrate from v2 to v3

Nuxt Content v3 has been rebuilt from the ground up, resulting in a new library with enhanced capabilities. While we've redesigned concepts and components in a similar way as Content v2, breaking changes are inevitable.

Don't worry, you don't need to modify your content files. We made sure that Content v3 handles content in the same way as Content v2.

Changes

Vue utils

  • queryContent() API is replaced with new queryCollection()
The new API is backed by SQL and content queries happens within a specific collection.
  • fetchContentNavigation() API is replaced with new queryCollectionNavigation()
  • Surroundings now has its own separate API queryCollectionItemSurroundings()
  • Document driven mode is dropped: Markdown files will not convert to Nuxt pages automatically, you need to create pages, check this section to see how.
  • useContent() composable is removed
  • searchContent() is dropped in favor of the new queryCollectionSearchSections API
  • Full text search can easily be done using the queryCollectionSearchSections API, check this section to see how

Components

  • All content should be rendered using <ContentRenderer> component. <ContentDoc>, <ContentList>, <ContentNavigation> and <ContentQuery> components are dropped in v3.
  • <ContentSlot> and <MDCSlot> components are not supported in v3. Instead components can simply use Vue's native <slot> component
<ContentSlot> and <MDCSlot> was initially pro to manipulate content before rendering and removing wrapping paragraphs from slot content. This unwrapping behavior is now supported via mdc-unwrap attribute in <slot> component. Example: <slot mdc-unwrap="p" />
  • Components created under the components/content directory are no longer automatically registered as global components. If you use dynamic rendering to render these components outside markdown files, you must manually register them in your Nuxt app. Check out the Nuxt - Custom Components Directories documentation for more information on how to do so.

Types

  • import type { NavItem } from '@nuxt/content/dist/runtime/types' is remplaced with import type { ContentNavigationItem } from '@nuxt/content'

General

  • _dir.yml files are renamed to .navigation.yml
  • There is no source option in module options, instead you can define multiple sources for your collections in content.config.ts.
  • Document ._path is now renamed to .path, likewise all internal fields with _ prefix are removed or renamed.
  • useContentHelpers() is removed

Nuxt Studio integration

  • The studio module has been deprecated and a new generic Preview API has been implemented directly into Nuxt Content, you can remove the @nuxthq/studio package from your dependencies and from the nuxt.config.ts modules. Instead we just need to enable the preview mode in the Nuxt configuration file by binding the Studio API.
nuxt.config.ts
export default defineNuxtConfig({
  content: {
    preview: {
      api: 'https://api.nuxt.studio
    }
  },
})
  • In order to keep the app config file updatable from Studio we just need to update the helper import of the nuxt.schema.ts file from @nuxthq/studio/theme to @nuxt/content/preview.

Implement Document Driven mode in v3

Implementing document driven mode in Content v3 is quite easy. All you need is to create a catch-all page in Nuxt and fetch contents based on route path.

pages/[...slug].vue
<script lang="ts" setup>
const route = useRoute()
const { data: page } = await useAsyncData(route.path, () => {
  return queryCollection('content').path(route.path).first()
})
</script>

<template>
  <div>
    <header><!-- ... --></header>

    <ContentRenderer v-if="page" :value="page" />

    <footer><!-- ... --></footer>
  </div>
</template>

Converting queryContent to queryCollections

As we mentioned above, queryContent is dropped in favor of new collection based queryCollection. There are two main differences between these two:

  1. queryCollection is building a query for an SQL database.
  2. queryCollection does the search only inside the specified collection. You should know the collection's name (key on config).
Find content with path
// Content v2
const v2Query = await queryContent(route.path).findOne()
// Content v3 - don't forget to create `content` collection in `content.config.ts`
const v3Query = await queryCollection('content').path(route.path).first()
Find contents with custom filter
// Content v2
const v2Query = await queryContent()
  .where({ path: /^\/hello\/.*/ })
  .find()
// Content v3 - don't forget to create `content` collection in `content.config.ts`
const v3Query = await queryCollection('content')
  .where('path', 'LIKE', '/hello%')
  .first()
Check the dedicated section for more info about collections

Convert queryContent().findSurround()

Surround now has its own separate API.

const targetPath = '/docs'

// Content v2
const v2Surround = await queryContent(targetPath)
  .only(['title', 'description', 'navigation'])
  .findSurround(withoutTrailingSlash(route.path))

// Content v3 - don't forget to create `content` collection in `content.config.ts`
const v3Surround = await queryCollectionItemSurroundings(
  'content',
  targetPath,
  {
    fields: ['title', 'description', 'navigation']
  }
)
Check the dedicated section for more information about the

Consolidate ProsePre, ProseCode, and ProseCodeInline components

Many ProsePre components are thin wrappers around the ProseCode component. We've consolidated these three components into two components. There is now no difference between ProsePre and multi-line code blocks.

  1. MDC will now map and parse single backticks ` as ProseCode instead of ProseCodeInline.
  2. MDC will now map and parse block code starting with three backticks``` as ProsePre component.

Suggested Changes:

  1. Your current ProseCode logic should be moved to ProsePre
  2. Rename your ProseCodeInline components to ProseCode