In Nuxt 4, when does a page file beside a same-named folder become a parent route, and what must that parent render?
answer
- file plus folder, same name
- children render inside the parent
- the NuxtPage outlet
- index.vue is the empty-path child
- default page key is the path
basics
~10 sIn Nuxt 4, app/pages/account.vue plus an app/pages/account/ folder makes account.vue a parent route whose folder files become children. The parent must render <NuxtPage /> where the child appears, or /account/orders shows only the parent.
solid answer
~40 sWhenever `app/pages/` holds both `account.vue` and an `account/` folder, Nuxt 4 nests the folder's pages as children of the `account.vue` route, so `/account/orders` matches the parent and then `account/orders.vue`. The parent is the shell (heading, account navigation) and must render `<NuxtPage />` as the outlet for the child; `account/index.vue` fills that outlet at `/account` itself. The same rule bites by accident: `recipes.vue` beside `recipes/[slug].vue` turns the list into a parent, so `/recipes/lemon-tart` shows the list with nowhere for the recipe to go, and renaming it to `recipes/index.vue` makes the two siblings. Because `<NuxtPage>` keys each level by its own path with params filled in, the account shell stays mounted while children swap, and a `[slug]` page remounts when the slug changes.
code
vue · 12 lines<!-- app/pages/account.vue: parent of app/pages/account/*.vue -->
<template>
<section class="account">
<nav>
<NuxtLink to="/account">Overview</NuxtLink>
<NuxtLink to="/account/orders">Orders</NuxtLink>
<NuxtLink to="/account/settings">Settings</NuxtLink>
</nav>
<!-- account/index.vue, orders.vue or settings.vue renders here -->
<NuxtPage />
</section>
</template>go deeper
Recall that a page file plus a same-named folder makes a parent route, and that the parent shows its child through <NuxtPage />.
Explain the generated record with children, why account/index.vue fills the outlet at /account, and why recipes.vue beside recipes/ captures the recipe URLs.
Reason about what stays mounted: the default key per level, when a [slug] page remounts, and when to override key or pageKey for state or transitions.
Weigh deep nesting against flat sibling pages plus layouts: nested shells keep shared state mounted, but they tie URL structure to component structure across teams.
## How a file and a folder become nested routes In **Nuxt 4**, routes come from `app/pages/`. When that directory holds a page file **and** a folder with the same name, such as `account.vue` next to `account/`, Nuxt does not produce unrelated routes. It produces one **parent route** for `account.vue` whose `children` are generated from the folder's files: | File | Route record | Full URL | |---|---|---| | `app/pages/account.vue` | parent, `path: '/account'` | renders for every child | | `app/pages/account/index.vue` | child, `path: ''` | `/account` | | `app/pages/account/orders.vue` | child, `path: 'orders'` | `/account/orders` | | `app/pages/account/settings.vue` | child, `path: 'settings'` | `/account/settings` | Child paths are relative to the parent. A request for `/account/orders` therefore matches **two** records in order: the parent, then the child. `route.matched` lists both, which is also why middleware and meta declared on the parent apply to its children. ## The parent is a shell with an outlet Matching a child is not the same as rendering it. Vue Router renders one component per matched level, and each level needs an outlet in the level above. In Nuxt that outlet is **`<NuxtPage />`**, Nuxt's wrapper around Vue Router's view outlet that adds page keys, transitions, `KeepAlive` wiring and `Suspense`. So `account.vue` is written as a shell: - shared chrome for the area: a heading, the account navigation, perhaps a summary panel; - a `<NuxtPage />` exactly where the child page belongs. Without that `<NuxtPage />`, `/account/orders` still matches and still runs its middleware, then shows only the shell. `account/index.vue` is the child with an empty path, so it fills the outlet at `/account`; leave it out and `/account` renders the shell around an empty outlet. `<NuxtPage>` forwards extra attributes to the child, so `<NuxtPage :user="user" />` in the parent reaches the child as a prop, a simple way to hand down data the shell already has. ## The accidental parent: recipes.vue The same rule causes a classic bug. A developer creates `app/pages/recipes.vue` as the recipe list and `app/pages/recipes/[slug].vue` as the detail page. Nuxt makes `recipes.vue` the parent of `[slug].vue`; the Nuxt docs phrase it as named parent routes taking priority over nested dynamic routes. `/recipes/lemon-tart` renders the list, and because the list has no `<NuxtPage />`, the recipe has nowhere to appear. The fix depends on intent: 1. If list and detail are independent pages, rename the list to `app/pages/recipes/index.vue`; it becomes a sibling of `[slug].vue` at `/recipes`. 2. If the list should stay visible beside the open recipe, a master-detail layout, keep `recipes.vue` and add `<NuxtPage />` where the detail belongs. ## What stays mounted: the default page key `<NuxtPage>` gives each level a **key**, and a changed key means a fresh component instance. By default the key is the matched route's own path with params filled in: | Navigation | Parent key | Child key | Effect | |---|---|---|---| | `/account/orders` to `/account/settings` | `/account`, unchanged | changes | shell stays mounted, child swaps | | `/recipes/lemon-tart` to `/recipes/apple-pie` | no parent | `/recipes/lemon-tart` to `/recipes/apple-pie` | page remounts | | `/account/orders?page=1` to `?page=2` | unchanged | unchanged | same instance; only `route.query` changes | This differs from a bare Vue Router outlet, which reuses a component when only its params change. In Nuxt, state inside the account shell (an open menu, a loaded profile) survives child navigation, while a `[slug]` page gets a fresh instance per slug. Two overrides exist: - `definePageMeta({ key: route => route.fullPath })` in the page itself; - the `pageKey` prop on the outlet: `<NuxtPage :page-key="route => route.fullPath" />`. Caching a page across navigations is opt-in, through `keepalive` in `definePageMeta` or on `<NuxtPage>`; how `KeepAlive` itself caches instances belongs to Vue. ## Named outlets Since Nuxt 4.5 one route can fill several outlets. A sibling file named with `@`, such as `[email protected]` beside `orders.vue`, renders into `<NuxtPage name="sidebar" />` in the parent, while `orders.vue` fills the default outlet. Outlets without a matching view stay empty, and `definePageMeta` is read only from the default file. ## Checklist - A same-named file and folder means parent and children, on purpose or not. - Every parent renders `<NuxtPage />`. - `index.vue` inside the folder fills the outlet at the parent's own URL. - Expect the shell to persist and `[slug]` pages to remount.
- The orders child keeps its state when only ?page= changes in the URL. Why, and how would you force a fresh instance per page number?`<NuxtPage>` keys a page by its route path with params filled in, and the query is not part of that key, so the same `orders.vue` instance is reused and only `route.query` changes. Usually I just watch `route.query.page`. If a fresh instance is really wanted, the child sets `definePageMeta({ key: route => route.fullPath })`, or the parent passes a `pageKey` to `<NuxtPage>`.
- Can the account shell show a second, route-dependent panel beside the main child?Since Nuxt 4.5, yes. A sibling file `[email protected]` next to `orders.vue` renders into `<NuxtPage name="sidebar" />` in the parent at `/account/orders`, while `orders.vue` fills the default outlet. Outlets with no matching named view stay empty, and `definePageMeta` in the `@sidebar` file has no effect; only the default file's meta counts.
saying these in an interview costs you the question
- The deeper file always wins, so recipes/[slug].vue ignores recipes.vue
- A Nuxt 4 parent page shows nested pages through <NuxtChild />
- Child pages render after the parent's template without any outlet
- Nested routes must be declared as a children array in nuxt.config.ts
- Moving between child routes remounts the parent shell each time