---
title: "Pages & Routing"
description: "Create page files for the Hot Springs Finder, learn how Nuxt's file-based routing compares to Next.js, and navigate between pages with NuxtLink."
canonical_url: "https://vercel.com/academy/nuxt-on-vercel/pages-and-routing"
md_url: "https://vercel.com/academy/nuxt-on-vercel/pages-and-routing.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-05-03T12:55:06.231Z"
content_type: "lesson"
course: "nuxt-on-vercel"
course_title: "Nuxt on Vercel"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Pages & Routing

# Pages & Routing

In Next.js, a route is a file called `page.tsx` inside a folder that matches the URL. Want `/about`? Create `app/about/page.tsx`. Want `/springs/[id]`? Create `app/springs/[id]/page.tsx`. The folder is the route, the file is always `page.tsx`.

Nuxt flips this. The file IS the route. Want `/about`? Create `app/pages/about.vue`. No wrapper folder, no mandatory filename. The trade-off is subtle but it matters: Next.js routes are verbose but explicit, Nuxt routes are compact but rely on you knowing the naming rules.

Let's build out the pages for Hot Springs Finder and see where those rules match up and where they diverge.

## Outcome

Create the route structure for the Hot Springs Finder with home, browse, and detail pages.

## Fast Track

1. Create page files in `app/pages/` for each route
2. Add `NuxtLink` navigation between pages
3. Verify routes resolve correctly in the browser

## Hands-on exercise 1.2

Build the page structure for the app. Right now, the starter has placeholder pages. We'll update them with real structure and connect them with navigation.

**Requirements:**

1. Verify these pages exist in `app/pages/` and understand which URL each one maps to
2. Update the home page with a link to the browse page using `NuxtLink`
3. Navigate between pages and confirm the routes work

**Implementation hints:**

- `NuxtLink` is the equivalent of Next.js's `Link` component. It's auto-imported, so you don't need to import it
- In Nuxt, `pages/springs/index.vue` maps to `/springs` and `pages/springs/[id].vue` maps to `/springs/:id`. In Next.js, those would be `app/springs/page.tsx` and `app/springs/[id]/page.tsx`
- Square brackets for dynamic segments work the same way conceptually, but the file structure is flatter

Here's the side-by-side for every route in our app:

```
URL              Nuxt file                      Next.js file
───              ─────────                      ────────────
/                pages/index.vue                app/page.tsx
/springs         pages/springs/index.vue        app/springs/page.tsx
/springs/:id     pages/springs/[id].vue         app/springs/[id]/page.tsx
/favorites       pages/favorites.vue            app/favorites/page.tsx
/visited         pages/visited.vue              app/visited/page.tsx
/login           pages/login.vue                app/login/page.tsx
```

Notice the pattern. Next.js always needs a folder. Nuxt lets you use either a folder with `index.vue` or a standalone file. `/favorites` doesn't need a `favorites/` folder because there are no nested routes under it. `/springs` uses a folder because it has a child route (`[id]`).

Now let's look at how navigation works. In Next.js, you'd write:

```tsx
import Link from "next/link";

<Link href="/springs">Browse</Link>
```

In Nuxt, `NuxtLink` is auto-imported. It accepts `href`, but `to` is the convention across Vue Router and most Vue UI libraries. We'll use `to`:

```vue
<NuxtLink to="/springs">Browse</NuxtLink>
```

The home page already uses this pattern. Let's look at the link that takes users from the home page to the browse page:

```vue title="app/pages/index.vue" {12-17}
<template>
  <div>
    <section>
      <h1>
        Find your next soak
      </h1>
      <p>
        Discover hot springs around the world. Browse by region, temperature, or
        type. Save your favorites and track the ones you've visited.
      </p>
      <div>
        <NuxtLink to="/springs">
          Browse Hot Springs
        </NuxtLink>
      </div>
    </section>
  </div>
</template>
```

`NuxtLink` handles client-side navigation automatically. No full page reload, no manual prefetching. When a `NuxtLink` enters the viewport, Nuxt prefetches the target route's code in the background. You get the performance behavior for free.

One thing that might catch you off guard: `useRoute()` in Nuxt returns a reactive route object, similar to Next.js's `useParams()` and `useSearchParams()` combined into one. We'll use this heavily when we build the detail page in a later lesson.

```vue
<script setup lang="ts">
const route = useRoute();
// route.params.id — same as useParams() in Next.js
// route.query.search — same as useSearchParams().get("search")
</script>
```

No import needed. `useRoute` is auto-imported like everything else in Nuxt.

\*\*Note: Navigating in Nuxt\*\*

`useRoute()` gives you the current route (read-only). For programmatic navigation — what Next.js's `useRouter().push()` does — Nuxt prefers `navigateTo("/springs")`, which works on the server and handles redirects cleanly. `useRouter()` exists if you need the raw router instance, but reach for `navigateTo` first.

\*\*Warning: Catch-all routes look different\*\*

In Next.js, a catch-all route is `[...slug]/page.tsx`. In Nuxt, it's `[...slug].vue`. Same concept, different syntax. We won't need one in this project, but it's a common gotcha when translating patterns.

## Try It

Start the dev server if it's not already running:

```bash
pnpm dev
```

1. Visit `http://localhost:3000`. Click "Browse Hot Springs." You should land on `/springs`
2. Check the URL bar. Click the browser back button. You should return to `/` without a full page reload
3. Visit `http://localhost:3000/springs/breitenbush-hot-springs` directly. You should see the detail page placeholder
4. Visit `http://localhost:3000/favorites` and `http://localhost:3000/visited`. Both should render their placeholder content

All six routes should resolve without 404 errors.

## Commit

```bash
git add -A && git commit -m "feat(routing): verify page routes and NuxtLink navigation"
```

## Done-When

- [ ] All six routes (`/`, `/springs`, `/springs/:id`, `/favorites`, `/visited`, `/login`) render without errors
- [ ] Clicking `NuxtLink` navigates without full page reloads
- [ ] You can explain when to use `useRoute()`, `navigateTo()`, and `useRouter()` in Nuxt
- [ ] You can map any Next.js route file to its Nuxt equivalent

## Solution

The starter already contains all the page files. The key files for routing are:

```
app/pages/
├── index.vue              → /
├── favorites.vue          → /favorites
├── visited.vue            → /visited
├── login.vue              → /login
└── springs/
    ├── index.vue          → /springs
    └── [id].vue           → /springs/:id
```

```vue title="app/pages/index.vue"
<template>
  <div>
    <section>
      <h1>
        Find your next soak
      </h1>
      <p>
        Discover hot springs around the world. Browse by region, temperature, or
        type. Save your favorites and track the ones you've visited.
      </p>
      <div>
        <NuxtLink to="/springs">
          Browse Hot Springs
        </NuxtLink>
      </div>
    </section>
  </div>
</template>
```

```vue title="app/pages/springs/index.vue"
<template>
  <div>
    <h1>
      Browse Hot Springs
    </h1>
    <p>
      Hot springs will be listed here once we wire up data fetching.
    </p>
  </div>
</template>
```

```vue title="app/pages/springs/[id].vue"
<template>
  <div>
    <h1>
      Spring Detail
    </h1>
    <p>
      Individual spring details will appear here once we build dynamic routes.
    </p>
  </div>
</template>
```


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
