---
title: "Nuxt 3.16: create nuxt, Unhead 2 and lazy hydration · vuejs.school"
description: "Nuxt 3.16 adds a tiny project creator, moves to Unhead 2, and lets a lazy component wait to hydrate until it is visible, idle or touched."
canonical_url: "https://vuejs.school/community/blog/nuxt-3-16-whats-new"
meta:
  "og:description": "Nuxt 3.16 adds a tiny project creator, moves to Unhead 2, and lets a lazy component wait to hydrate until it is visible, idle or touched."
  "og:image": "https://vuejs.school/_og/d/a_~TnV4dCAzLjE2OiBjcmVhdGUgbnV4dCwgVW5oZWFkIDIgYW5kIGxhenkgaHlkcmF0aW9uIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~TnV4dCAzLjE2OiBjcmVhdGUgbnV4dCwgVW5oZWFkIDIgYW5kIGxhenkgaHlkcmF0aW9u%2Cdescription_~TnV4dCAzLjE2IGFkZHMgYSB0aW55IHByb2plY3QgY3JlYXRvciwgbW92ZXMgdG8gVW5oZWFkIDIsIGFuZCBsZXRzIGEgbGF6eSBjb21wb25lbnQgd2FpdCB0byBoeWRyYXRlIHVudGlsIGl0IGlzIHZpc2libGUsIGlkbGUgb3IgdG91Y2hlZC4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LTMtMTYtd2hhdHMtbmV3Ig%2Cs_oA7_OQ-Zg6FPZLZk.png"
  "og:image:alt": "Nuxt 3.16: create nuxt, Unhead 2 and lazy hydration · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Nuxt 3.16: create nuxt, Unhead 2 and lazy hydration · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~TnV4dCAzLjE2OiBjcmVhdGUgbnV4dCwgVW5oZWFkIDIgYW5kIGxhenkgaHlkcmF0aW9uIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~TnV4dCAzLjE2OiBjcmVhdGUgbnV4dCwgVW5oZWFkIDIgYW5kIGxhenkgaHlkcmF0aW9u%2Cdescription_~TnV4dCAzLjE2IGFkZHMgYSB0aW55IHByb2plY3QgY3JlYXRvciwgbW92ZXMgdG8gVW5oZWFkIDIsIGFuZCBsZXRzIGEgbGF6eSBjb21wb25lbnQgd2FpdCB0byBoeWRyYXRlIHVudGlsIGl0IGlzIHZpc2libGUsIGlkbGUgb3IgdG91Y2hlZC4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LTMtMTYtd2hhdHMtbmV3Ig%2Cs_oA7_OQ-Zg6FPZLZk.png"
  "twitter:image:alt": "Nuxt 3.16: create nuxt, Unhead 2 and lazy hydration · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## Start a project with npm create nuxt

The [release post](https://nuxt.com/blog/v3-16) leads with `create-nuxt`, a new package for starting projects. It does the same job as `nuxi init`, but it is about one sixth of the size, bundled as a single file with its dependencies inlined, so there is less to download before you can start. Run it from the folder that should hold the new project:

```
npm create nuxt
```

It asks where to create the project (`nuxt-app` unless you type another name), which package manager to use and whether to initialise a git repository. It does not ask about modules yet. The post links to the CLI roadmap, where an interactive module selector is planned; for now, add modules afterwards with `npx nuxi module add <name>`.

## Delay hydration on Lazy components

Hydration is the step where the browser takes the HTML the server already rendered and attaches Vue to it: creating component instances, wiring event listeners, making the page interactive. Every component on the page pays that cost on load, including a footer nobody has scrolled to and a dropdown nobody has opened.

Vue 3.5 added hydration strategies that postpone this work per component. Nuxt 3.16 exposes them as props on `Lazy` components (an auto-imported component with the `Lazy` prefix, which Nuxt loads as a separate chunk), so you no longer need `defineAsyncComponent` by hand. This home page delays five components in five different ways:

*pages/index.vue*

```
<script setup lang="ts">
function onFooterReady() {
  console.log('Footer is interactive')
}
</script>

<template>
  <main>
    <HeroBanner />
    <LazyProductGrid hydrate-on-visible />
    <LazySearchDropdown hydrate-on-interaction="mouseover" />
    <LazyMobileMenu hydrate-on-media-query="(max-width: 768px)" />
    <LazyNewsletterForm hydrate-on-idle />
    <LazySiteFooter :hydrate-after="2000" @hydrated="onFooterReady" />
  </main>
</template>
```

Each prop names a trigger:

- `hydrate-on-visible` waits until the component scrolls into view.
- `hydrate-on-interaction` waits for an event, here a mouseover. The event that woke the component is replayed once hydration is done.
- `hydrate-on-media-query` hydrates only when the query matches, so the mobile menu stays inert on a wide screen.
- `hydrate-on-idle` waits until the browser is idle.
- `hydrate-after` waits a number of milliseconds.

The `@hydrated` event fires once hydration has happened, which is useful for analytics or for starting something that needs the live component.

The HTML is still rendered on the server, so the content is visible and crawlable from the first response. Only the JavaScript work waits. Nuxt's docs for 3.16 say the props take effect only when:

- the component has the `Lazy` prefix (a plain `<ProductGrid />` ignores them);
- the prop is written in the template of a `.vue` file, not spread from an object with `v-bind`;
- the component is auto-imported, not imported directly from `#components`;
- it uses one strategy, not several.

## Unhead 2: check where useHead comes from

`useHead()` and `useSeoMeta()` are powered by Unhead, the library that manages the tags in `<head>`. Nuxt 3.16 moves to Unhead 2, which is still a release candidate on npm. The major version removes deprecations and reworks how Unhead finds its context. Nuxt 3 apps get a legacy compatibility build, so existing calls keep working, and Nuxt now re-exports the composables itself while resolving the context properly.

The check is about where your imports come from:

- The auto-imported `useHead` is fine.
- An explicit import from `#app/composables/head` is fine.
- An import straight from `@unhead/vue` is the one to change. The release post says it might lose async context: Nuxt's record of which app, and on the server which request, a call belongs to.

In 3.16 the change is recommended rather than required. During `nuxi dev` and `nuxi build`, Nuxt rewrites these imports to its own version and prints a warning that starts "You are importing from `@unhead/vue` in" and names the file. Search your project for `from '@unhead/vue'` and swap each one:

*composables/usePageTitle.ts*

```
// Before: import { useHead } from '@unhead/vue'
import { useHead } from '#app/composables/head'

export function usePageTitle(title: string) {
  useHead({ title: `${title} · My Shop` })
}
```

If you opted into `future.compatibilityVersion: 4` in `nuxt.config.ts`, you get Unhead 2 without the compatibility build. The [upgrade guide's Unhead v2 section](https://nuxt.com/docs/getting-started/upgrade#unhead-v2) lists what changes: the `hid`, `vmid`, `children` and `body` props are gone, promises are no longer accepted as input, tags are sorted with Capo.js, and Template Params and Alias Sorting need their plugins. `unhead: { legacy: true }` brings the old behaviour back while you fix them.

## Faster startup and new unjs versions

The release post lists five pieces of performance work:

- `exsolve` for module resolution
- a shorter list of resolution paths that tries direct imports first
- no duplicated Nitro alias resolution
- a `loadNuxt` that skips steps it does not need
- `oxc-parser` for parsing Nuxt plugins

The team measured the changes with CodSpeed benchmarks in CI. The post also gives two anecdotal figures: roe.dev loads 32% faster with 3.16, and nuxt.com 28% faster. Your numbers will differ, but `nuxi dev` should come up sooner.

The release also moves to new major versions of the unjs packages Nuxt is built on: `unenv` 2 (a release candidate), `ohash` 2, `untyped` 2, `c12` 3, `pathe` 2, `cookie-es` 2, `unimport` 4 and `db0` 0.3, plus `esbuild` 0.25 and `chokidar` 4. Several of them now ship only ES modules. Your app does not notice, but code that loads them with `require()` does. If a CommonJS script of your own calls `require('ohash')` and gets version 2, it throws `ERR_REQUIRE_ESM` on Node 18 and 20. Node 22.12 and later can load an ES module that way.

## Layer aliases and new config options

Four smaller additions:

- **Named layer aliases.** A layer is a Nuxt project that another project extends. Layers inside `~~/layers` ( `~~` is the project root) now get an alias with no config: `~~/layers/shop` is available as `#layers/shop`, so you can import from it without relative paths. A layer anywhere else can declare `$meta: { name: 'shop' }` in its config to get the same alias.
- **`pages.pattern`** tells Nuxt which files count as pages. By default any `.js`, `.ts`, `.vue` or similar file in `pages/` becomes a route; `pattern: ['**/*.vue']` stops a stray `.ts` file there from becoming one.
- **Granular `debug`.** The option takes flags ( `templates`, `modules`, `watchers`, `hooks`, `nitro`, `router`, `hydration`) instead of the all-or-nothing `debug: true`, which still works and enables everything.
- **Decorators**, behind `experimental.decorators: true`. As with every experimental feature, the team asks for feedback.

## DevTools 2, clearer errors and Nitro 2.11

- Nuxt DevTools moves to version 2, with a custom editor setting, a view of the resolved config, a way to track how modules modify your config, the schema generator back, and fewer dependencies.
- `useAsyncData` calls that return `undefined` now warn with an exact file location.
- Island page errors show the error page correctly.
- Terminal errors go through Nitro's error handling, with clearer messages and stack traces, and Nitro applies source maps without extra Node options.
- Nitro itself moves to 2.11; its [release notes](https://github.com/nitrojs/nitro/releases/tag/v2.11.0) list the changes.

## How to upgrade and what to check first

One command refreshes the lockfile and pulls the new dependency versions, including the unjs packages:

```
npx nuxi@latest upgrade --dedupe
```

Then run through this list before you deploy:

1. Import `useHead`, `useSeoMeta` and the other head composables from Nuxt, not `@unhead/vue`. The build warning names any file you missed.
2. If you use `compatibilityVersion: 4`, work through the Unhead v2 section of the upgrade guide.
3. If a module or script of yours loads a unjs package with `require()`, check it still runs on your Node version.
4. Start `nuxi dev`, open DevTools 2, and look at the resolved config for anything a module changed unexpectedly.
5. Run your tests, and compare the page source of a key page before and after: the `<head>` is where an Unhead change would show.

**Check it stuck**3 questions from the SSR and hydration quiz.

[**Practice now**](https://vuejs.school/learn/nuxt/ssr-and-hydration?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Release notes · 7 min read**What's new in Nuxt 4.6: nuxt/server, typed $fetch, cookie sessions and Vapor**](https://vuejs.school/community/blog/whats-new-in-nuxt-4-6) [Tutorial · 6 min read**useFetch or $fetch? Picking the right one in Nuxt**](https://vuejs.school/community/blog/usefetch-or-fetch-picking-the-right-one-in-nuxt)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/nuxt-3-16-whats-new#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-03-11","description":"Nuxt 3.16 adds a tiny project creator, moves to Unhead 2, and lets a lazy component wait to hydrate until it is visible, idle or touched.","name":"Nuxt 3.16: create nuxt, Unhead 2 and lazy hydration","url":"https://vuejs.school/community/blog/nuxt-3-16-whats-new","breadcrumb":{"@id":"https://vuejs.school/community/blog/nuxt-3-16-whats-new#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/nuxt-3-16-whats-new"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/nuxt-3-16-whats-new#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-03-11","description":"Nuxt 3.16 adds a tiny project creator, moves to Unhead 2, and lets a lazy component wait to hydrate until it is visible, idle or touched.","headline":"Nuxt 3.16: create nuxt, Unhead 2 and lazy hydration","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~TnV4dCAzLjE2OiBjcmVhdGUgbnV4dCwgVW5oZWFkIDIgYW5kIGxhenkgaHlkcmF0aW9uIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~TnV4dCAzLjE2OiBjcmVhdGUgbnV4dCwgVW5oZWFkIDIgYW5kIGxhenkgaHlkcmF0aW9u%2Cdescription_~TnV4dCAzLjE2IGFkZHMgYSB0aW55IHByb2plY3QgY3JlYXRvciwgbW92ZXMgdG8gVW5oZWFkIDIsIGFuZCBsZXRzIGEgbGF6eSBjb21wb25lbnQgd2FpdCB0byBoeWRyYXRlIHVudGlsIGl0IGlzIHZpc2libGUsIGlkbGUgb3IgdG91Y2hlZC4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LTMtMTYtd2hhdHMtbmV3Ig%2Cs_oA7_OQ-Zg6FPZLZk.png","@type":["Article","BlogPosting"],"articleSection":["Release notes"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/nuxt-3-16-whats-new#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/nuxt-3-16-whats-new#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/nuxt-3-16-whats-new#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"Nuxt 3.16: create nuxt, Unhead 2 and lazy hydration","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~TnV4dCAzLjE2OiBjcmVhdGUgbnV4dCwgVW5oZWFkIDIgYW5kIGxhenkgaHlkcmF0aW9uIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~TnV4dCAzLjE2OiBjcmVhdGUgbnV4dCwgVW5oZWFkIDIgYW5kIGxhenkgaHlkcmF0aW9u%2Cdescription_~TnV4dCAzLjE2IGFkZHMgYSB0aW55IHByb2plY3QgY3JlYXRvciwgbW92ZXMgdG8gVW5oZWFkIDIsIGFuZCBsZXRzIGEgbGF6eSBjb21wb25lbnQgd2FpdCB0byBoeWRyYXRlIHVudGlsIGl0IGlzIHZpc2libGUsIGlkbGUgb3IgdG91Y2hlZC4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LTMtMTYtd2hhdHMtbmV3Ig%2Cs_oA7_OQ-Zg6FPZLZk.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~TnV4dCAzLjE2OiBjcmVhdGUgbnV4dCwgVW5oZWFkIDIgYW5kIGxhenkgaHlkcmF0aW9uIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~TnV4dCAzLjE2OiBjcmVhdGUgbnV4dCwgVW5oZWFkIDIgYW5kIGxhenkgaHlkcmF0aW9u%2Cdescription_~TnV4dCAzLjE2IGFkZHMgYSB0aW55IHByb2plY3QgY3JlYXRvciwgbW92ZXMgdG8gVW5oZWFkIDIsIGFuZCBsZXRzIGEgbGF6eSBjb21wb25lbnQgd2FpdCB0byBoeWRyYXRlIHVudGlsIGl0IGlzIHZpc2libGUsIGlkbGUgb3IgdG91Y2hlZC4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LTMtMTYtd2hhdHMtbmV3Ig%2Cs_oA7_OQ-Zg6FPZLZk.png"}]}
```