Migration

Migrate Nuxt UIPro Documentation Starter

How to upgrade your Nuxt UI Pro documentation to Content and UIPro v3

Baptiste Leproux

@larbish

How to upgrade your Nuxt UI Pro documentation to Content and UI v3

2025 kicks off with the power of 3!

This start of year is marked by major updates to our favorite tools. The UI team is about to launch version 3 of the UI / UI Pro libraries (currently in alpha), while the Content team has already released Nuxt Content v3.

These updates mean that all our starter templates combining Content and UI will need to be updated to align with the latest versions. To help you make the transition, this guide walks through migrating the Docs Starter to the new Content x UI v3 package.

Check the UI Pro documentation starter repository code source.

Content migration (v2 → v3)

1. Update package to v3

pnpm add @nuxt/content@next

2. Create content.config.ts file

This configuration file define your data structure. A collection represents a set of related items. In the case of the docs starter, there is two different collections, the landing collection representing the home page, one for the landing page and another for the doc pages.

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

export default defineContentConfig({
  collections: {
    landing: defineCollection({
      type: 'page',
      source: 'index.yml'
    }),
    docs: defineCollection({
      type: 'page',
      source: {
        include: '**',
        exclude: ['index.yml']
      },
      schema: z.object({
        links: z.array(z.object({
          label: z.string(),
          icon: z.string(),
          to: z.string(),
          target: z.string().optional()
        })).optional()
      })
    })
  }
})

On top of the built-in fields provided by the page type, the docs collection needs links extra field that can be optionally display in the docs page header.

The type: page means there is a 1-to-1 relationship between the content file and a page on your site.

3. Migrate app.vue

const { data: navigation } = await useAsyncData('navigation', () => fetchContentNavigation())

Content search command palette data can use the new queryCollectionSearchSections method

const { data: files } = useLazyFetch<ParsedContent[]>('/api/search.json', {
  default: () => [],
  server: false
})

4. Migrate landing page

Home page data fetching can be updated by moving from queryContent to queryCollection method

const { data: page } = await useAsyncData('index', () => queryContent('/').findOne())

useSeoMeta can be populated using the seo field provided by the page type

index.vue
useSeoMeta({
  title: page.value.seo.title,
  ogTitle: page.value.seo.title,
  description: page.value.seo.description,
  ogDescription: page.value.seo.description
})
Please note that the seo field is automatically overridden by the root title and description if empty.

5. Migrate catch-all docs page

Docs page data and surround fetching can be updated and mutualised by moving from queryContent to queryCollection and queryCollectionItemSurroundings methods

const { data: page } = await useAsyncData(route.path, () => queryContent(route.path).findOne())

const { data: surround } = await useAsyncData(`${route.path}-surround`, () => queryContent()
  .where({ _extension: 'md', navigation: { $ne: false } })
  .only(['title', 'description', '_path'])
  .findSurround(withoutTrailingSlash(route.path))
)

Populate useSeoMeta with the seo field provided by the page type

index.vue
useSeoMeta({
  title: page.value.seo.title,
  ogTitle: `${page.value.seo.title} - ${seo?.siteName}`,
  description: page.value.seo.description,
  ogDescription: page.value.seo.description
})
Please note that the seo field is automatically overridden by the root title and description if empty.

6. Update types

Types have been significantly enhanced in Content v3, eliminating the need for most manual typings, as they are now directly provided by the Nuxt Content APIs.

Concerning the documentation starter, the only typing needed concerns the navigation items where NavItem can be replaced by ContentNavigationItem .

import type { ContentNavigationItem } from '@nuxt/content'

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

7. Replace folder metadata files

All _dir.yml files become .navigation.yml

8. Migrate Studio activation

Since the studio module has been deprecated and a new generic preview API has been implemented directly into Nuxt Content, we can remove the @nuxthq/studio package from our dependancies 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'
    }
  },
})

Finally, 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 .

That's it, content v3 is now powering the starter. Let's now migrate to the version 3 of Nuxt UI / UIPro.

Nuxt UIPro Migration (v1 → v3)

This is a migration case, it won't cover all breaking changes introduced by the version upgrade. You should check each component you're using in the documentation to know if you need updates concerning props, slots or styles.

1. Setup package to v3

To maintain consistency with the UI versioning, which transitioned from v1 to v2. The Nuxt UIPro version 2 is being skipped, and the update jumps directly to v3.

Install the Nuxt UI v3 alpha package

pnpm add @nuxt/ui-pro@next

Add the module in the Nuxt configuration file

export default defineNuxtConfig({
  extends: ['@nuxt/ui-pro']
})
Nuxt UIPro V3 is now considered as a module and no more as a layer.

Import Tailwind CSS and Nuxt UI Pro in your CSS

assets/css/main.css
@import "tailwindcss";
@import "@nuxt/ui-pro";
nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/ui-pro'],
  css: ['~/assets/css/main.css']
})

Remove tailwind config file and use CSS-first theming

Nuxt UI v3 uses Tailwind CSS v4 that follows a CSS-first configuration approach, you can now customize your theme with CSS variables inside a @theme directive.

  • Delete the tailwind.config.ts file
  • Use the @theme directive to apply your theme in main.css file
  • Use the @source directive in order for Tailwind to detect classes in markdown files.
assets/css/main.css
@import "tailwindcss";
@import "@nuxt/ui-pro";

@source "../content/**/*";

@theme {
  --font-sans: 'DM Sans', sans-serif;

  --color-green-50: #EFFDF5;
  --color-green-100: #D9FBE8;
  --color-green-200: #B3F5D1;
  --color-green-300: #75EDAE;
  --color-green-400: #00DC82;
  --color-green-500: #00C16A;
  --color-green-600: #00A155;
  --color-green-700: #007F45;
  --color-green-800: #016538;
  --color-green-900: #0A5331;
  --color-green-950: #052E16;
}

2. Update ui overloads in app.config.ts

All overloads using the ui props in a component or the ui key in the app.config.ts are obsolete and need to be check in the UI / UI Pro documentation.
export default defineAppConfig({
  ui: {
    primary: 'green',
    gray: 'slate',
    footer: {
      bottom: {
        left: 'text-sm text-gray-500 dark:text-gray-400',
        wrapper: 'border-t border-gray-200 dark:border-gray-800'
      }
    }
  },
})

3. Update app.vue

  • Main, Footer and LazyUContentSearch components do not need any updates in our case.
  • Notification component can be removed since Toast components are directly handled by the App component.
  • Header component needs updates:
    • panel slot has been replaced by content.
    • logo slot has been replaced by title.
    • center slot has been removed and is now the default.
  • Instead of the NavigationTree component you can use the NavigationMenu component or the ContentNavigation component to display content navigation.
<script>
// Content navigation provided by fetchContentNavigation()
const navigation = inject<Ref<NavItem[]>>('navigation')
</script>

<template>
  <UHeader>
    <template #panel>
      <UNavigationTree :links="mapContentNavigation(navigation)" />
     </template>
   </UHeader>
</template>

4. Update landing page

We've decided to move the landing content from YML to Markdown .

This decision was made because components used in Markdown no longer need to be exposed globally (nor do they need to be created in the components/content folder). Content v3 handles it under the hood.

Update content configuration

content.config.ts
export default defineContentConfig({
  collections: {
    landing: defineCollection({
      type: 'page',
      source: 'index.md'
    }),
    docs: defineCollection({
      type: 'page',
      source: {
        include: '**',
        exclude: ['index.md']
      },
      ...
    })
  }
})

Use ContentRenderer to render Markdown

prose property must be set to false in ContentRendered as we don't want Mardown to be applied with prose styling in the case of a landing page integrating non prose Vue components.
<template>
  <div>
    <ULandingHero
      v-if="page.hero"
      v-bind="page.hero"
    >
      <template #headline>
        <UBadge
          v-if="page.hero.headline"
          variant="subtle"
          size="lg"
          class="relative rounded-full font-semibold"
        >
          <NuxtLink
            :to="page.hero.headline.to"
            target="_blank"
            class="focus:outline-none"
            tabindex="-1"
          >
            <span
              class="absolute inset-0"
              aria-hidden="true"
            />
          </NuxtLink>

          {{ page.hero.headline.label }}

          <UIcon
            v-if="page.hero.headline.icon"
            :name="page.hero.headline.icon"
            class="ml-1 w-4 h-4 pointer-events-none"
          />
        </UBadge>
      </template>

      <template #title>
        <MDC :value="page.hero.title" />
      </template>

      <MDC
        :value="page.hero.code"
        class="prose prose-primary dark:prose-invert mx-auto"
      />
    </ULandingHero>

    <ULandingSection
      :title="page.features.title"
      :links="page.features.links"
    >
      <UPageGrid>
        <ULandingCard
          v-for="(item, index) of page.features.items"
          :key="index"
          v-bind="item"
        />
      </UPageGrid>
    </ULandingSection>
  </div>
</template>

Migrate Vue components to MDC

Move all components in index.md following the MDC syntax.

Landing components have been reorganised and standardised as generic Page components.

  • LandingHero => PageHero
  • LandingSection => PageSection
  • LandingCard => PageCard (we'll use the PageFeature instead)

.

Have a look at the final Markdown result on GitHub.

4. Migrate docs page

Layout

  • Aside component has been renamed to PageAside .
  • ContentNavigation component can be used (instead of NavigationTree) to display the content navigation returned by queryCollectionNavigation.

.

<template>
  <UContainer>
    <UPage>
      <template #left>
        <UAside>
          <UNavigationTree :links="mapContentNavigation(navigation)" />
        </UAside>
      </template>

      <slot />
    </UPage>
  </UContainer>
</template>

Catch-all pages

  • Divider has been renamed in Separator
  • FindPageHeadline must be imported from #ui-pro/utils/content
  • prose property does not exist no more on PageBody component.
That's it! The docs starter is now fully running on both UI and Content v3 🎉 You can have a check at the source code on GitHub.

Bonus: Edit on Studio

Start with Studio today

gradient cta