---
title: "Pagination SEO in Nuxt"
description: "Give paginated Nuxt pages unique URLs, self-referencing canonicals, and crawlable links. Check that crawlers can reach each page."
canonical_url: "https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/pagination"
last_updated: "2026-10-05"
---

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

::key-takeaways
- Each paginated page needs its own self-referencing canonical; never point every page's canonical at page 1
- Google retired `rel=prev/next` as an indexing signal in 2019, so crawlable `<a href>`{lang="html"} links are what get pages 2+ discovered now
- Infinite scroll needs a hybrid approach: real content for users, paginated URLs with unique canonicals for crawlers
::

Pagination splits content across multiple pages. The content and database snippets require your own collection schema, database client, and total count. Those data-loading snippets cover initial page setup; add route-change handling if the component remains mounted. Each page needs a distinct URL and appropriate canonical. Correct setup helps discovery but does not guarantee indexing or rankings.

Google no longer uses [rel=prev/next tags](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading) as an indexing signal; it retired them in 2019. Modern pagination relies on self-referencing canonicals and crawlable `<a href>`{lang="html"} links instead.

## Self-Referencing Canonical Tags

Each paginated page should have its own canonical URL pointing to itself.

**Don't** point distinct pages to page 1. This requests consolidation of different content. Google may ignore that preference.

::code-group
```vue [✅ Correct]
<script setup lang="ts">
const route = useRoute()
const page = computed(() => Number(route.query.page) || 1)

useHead(() => ({
  link: [
    {
      rel: 'canonical',
      href: `https://nuxtseo.com/blog?page=${page.value}`
    }
  ]
}))
</script>
```

```vue [❌ Wrong]
<script setup lang="ts">
// Avoid this preference for distinct pages; Google may ignore it
useHead({
  link: [
    {
      rel: 'canonical',
      href: 'https://nuxtseo.com/blog' // Always page 1
    }
  ]
})
</script>
```
::

Google's own guidance is explicit: [don't use the first page of a sequence as the canonical for every page](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading); give each page its own canonical URL instead.

## Pagination URL Structure

Use query parameters or path segments. Both work for SEO.

| URL Pattern     | Example        | SEO Impact               |
| --------------- | -------------- | ------------------------ |
| Query parameter | `/blog?page=2` | Good - simple, flexible  |
| Path segment    | `/blog/page/2` | Good - cleaner URLs      |
| Hash fragment   | `/blog#page=2` | Bad - Google ignores `#` |

**Query parameter approach:**

Nuxt handles query parameters automatically through file-based routing:

```vue [pages/blog.vue]
<script setup lang="ts">
const route = useRoute()
const page = computed(() => Number(route.query.page) || 1)

// Fetch paginated content
const { data: posts } = await useAsyncData(
  'posts',
  () => queryCollection('blog')
    .skip((page.value - 1) * 10)
    .limit(10)
    .all(),
  { watch: [page] }
)
</script>
```

**Path segment approach:**

Create nested route structure for cleaner URLs:

```vue [pages/blog/index.vue]
<script setup lang="ts">
// Handles /blog
const { data: posts } = await useAsyncData(
  'posts',
  () => queryCollection('blog').limit(10).all()
)
</script>
```

```vue [pages/blog/page/[page\\].vue]
<script setup lang="ts">
// Handles /blog/page/2
const route = useRoute()
const page = Number(route.params.page)

const { data: posts } = await useAsyncData(
  'posts',
  () => queryCollection('blog')
    .skip((page - 1) * 10)
    .limit(10)
    .all()
)
</script>
```

Never use fragment identifiers (`#page=2`). [Google ignores everything after `#`](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading) and may not follow a "next page" link that only differs by its fragment, since it looks identical to the page already crawled.

## Crawlable Links

Google needs `<a href>`{lang="html"} tags to discover paginated pages. Nuxt's SSR renders navigation in the initial HTML.

```vue
<script setup lang="ts">
const route = useRoute()
const currentPage = computed(() => Number(route.query.page) || 1)
const totalPages = 10
</script>

<template>
  <nav>
    <!-- Crawlable links with href -->
    <NuxtLink
      v-for="n in totalPages"
      :key="n"
      :to="{ query: { page: n } }"
      :class="{ active: n === currentPage }"
    >
      {{ n }}
    </NuxtLink>
  </nav>
</template>
```

`NuxtLink` generates proper `<a href>`{lang="html"} tags while providing client-side navigation for users.

## Pagination Component

Full example with prev/next links and numbered pages:

```vue
<script setup lang="ts">
const route = useRoute()
const currentPage = computed(() => Number(route.query.page) || 1)
const totalPages = 10

const prevPage = computed(() =>
  currentPage.value > 1 ? currentPage.value - 1 : null
)
const nextPage = computed(() =>
  currentPage.value < totalPages ? currentPage.value + 1 : null
)
</script>

<template>
  <nav aria-label="Pagination">
    <!-- Previous link -->
    <NuxtLink
      v-if="prevPage"
      :to="{ query: { page: prevPage } }"
    >
      Previous
    </NuxtLink>

    <!-- Page numbers -->
    <NuxtLink
      v-for="n in totalPages"
      :key="n"
      :to="{ query: { page: n } }"
      :aria-current="n === currentPage ? 'page' : undefined"
    >
      {{ n }}
    </NuxtLink>

    <!-- Next link -->
    <NuxtLink
      v-if="nextPage"
      :to="{ query: { page: nextPage } }"
    >
      Next
    </NuxtLink>
  </nav>
</template>
```

[Include links from each page to following pages](https://www.amsive.com/insights/seo/how-to-correctly-implement-pagination-for-seo-user-experience/) using `<a href>`{lang="html"} tags. Googlebot follows these to discover your content.

## View All Page Approach

A View All page can give readers another way to browse the collection. Keep distinct paginated pages self-canonical. A View All page's existence alone does not make each subset an equivalent duplicate. Consolidate only equivalent content; do not use noindex to choose a canonical.

## Infinite Scroll vs Pagination

| Pattern          | SEO Impact                       | UX           | When to Use               |
| ---------------- | -------------------------------- | ------------ | ------------------------- |
| Pagination       | Good - all pages indexable       | Predictable  | Catalogs, search results  |
| Infinite scroll  | Poor - requires special handling | Frictionless | Social feeds, inspiration |
| Load More button | Poor - unless URL changes        | Balanced     | Product listings          |

**Infinite scroll SEO challenges:**

Google Search [doesn't scroll or click](https://developers.google.com/search/docs/crawling-indexing/javascript/lazy-loading) to load content, so anything that only appears after a scroll event stays hidden from it. Fix it with a hybrid approach: run infinite scroll for users, and update the URL as they scroll so crawlers have a paginated series to follow. Give each increment a unique, persistent URL with an absolute page number (`?page=12`, not something relative like `?date=yesterday`), and link sequentially between pages.

Use an app-owned loading function for the button. Handle absent or failed API responses before appending records. The URL must also load the same page directly; changing browser history alone does not make new content crawlable.

**Load More button:**

Buttons work for readers, but [Googlebot cannot click them](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading). Use the hybrid approach above for crawlable pagination.

## Server-Side Pagination

Supply your database client and total record count. These are application dependencies, not Nuxt globals.

Nuxt Content provides built-in pagination support:

```vue [pages/blog.vue]
<script setup lang="ts">
const route = useRoute()
const page = computed(() => Number(route.query.page) || 1)
const limit = 10

const { data: posts } = await useAsyncData(
  'posts',
  () => queryCollection('blog')
    .skip((page.value - 1) * limit)
    .limit(limit)
    .all(),
  { watch: [page] }
)

const { data: totalPosts } = await useAsyncData(
  'totalPosts',
  () => queryCollection('blog').count()
)

const totalPages = computed(() => Math.ceil(totalPosts.value / limit))
</script>
```

For API-based pagination:

```ts [server/api/posts.get.ts]
export default defineEventHandler(async (event) => {
  const query = getQuery(event)
  const page = Number(query.page) || 1
  const limit = 10
  const offset = (page - 1) * limit

  const posts = await db.query(
    'SELECT * FROM posts LIMIT ? OFFSET ?',
    [limit, offset]
  )

  return {
    posts,
    page,
    totalPages: Math.ceil(totalPosts / limit)
  }
})
```

Limit queries to avoid performance issues. Use database indexes on sort columns.

## Meta Titles and Descriptions

Differentiate each paginated page for SEO:

```vue
<script setup lang="ts">
const route = useRoute()
const page = computed(() => Number(route.query.page) || 1)

useHead(() => ({
  title: page.value === 1
    ? 'Blog Posts'
    : `Blog Posts - Page ${page.value}`,
  meta: [
    {
      name: 'description',
      content: page.value === 1
        ? 'Read our latest blog posts about Nuxt SEO.'
        : `Blog posts page ${page.value}. Read more about Nuxt SEO.`
    }
  ],
  link: [
    {
      rel: 'canonical',
      href: `https://nuxtseo.com/blog${page.value > 1 ? `?page=${page.value}` : ''}`
    }
  ]
}))
</script>
```

You may add the page number for clarity. [Google allows the same title and description across a pagination sequence](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading).

## Noindex on Paginated Pages

Keep useful, distinct paginated pages indexable. If your policy excludes a selected page, use noindex and allow crawling so Google can read it. A View All page alone does not justify excluding the pagination sequence.

## Common Mistakes

**Mistake 1: Canonicalizing all pages to page 1**

```vue
<!-- ❌ Don't do this -->
<script setup lang="ts">
useHead({
  link: [
    { rel: 'canonical', href: 'https://nuxtseo.com/blog' }
  ]
})
</script>
```

This requests consolidation of pages 2+. Use self-referencing canonicals for distinct content.

**Mistake 2: Using hash fragments**

```
❌ /blog#page=2
✅ /blog?page=2
✅ /blog/page/2
```

Google ignores `#`. Your pagination won't be indexed.

**Mistake 3: Client-only pagination links**

```vue
<!-- ❌ Not crawlable -->
<button @click="nextPage">
Next
</button>

<!-- ✅ Crawlable -->
<NuxtLink :to="{ query: { page: page + 1 } }">
Next
</NuxtLink>
```

**Mistake 4: Inconsistent trailing slashes**

```
/blog?page=1
/blog/?page=2  ← Duplicate content
```

Pick one format. See [trailing slashes guide](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes).

**Mistake 5: Blocking pagination in robots.txt**

```robots-txt
# ❌ Hides paginated content
User-agent: *
Disallow: /*?page=
```

This wildcard prevents Google from fetching pages 2+ and reading their canonical tags. A linked blocked URL can still appear without its content. Google's [own guidance](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading) reserves robots.txt for filter and sort URL variants (`?sort=price`, `?color=red`), not the primary page sequence.

## Testing Pagination SEO

**1. Check canonical tags**

```bash
curl -s https://nuxtseo.com/blog?page=2 | grep canonical
```

Should return:

```html
<link rel="canonical" href="https://nuxtseo.com/blog?page=2">
```

**2. Verify crawlable links**

View page source (not DevTools). Look for `<a href>`{lang="html"} tags with pagination URLs. Nuxt's SSR ensures these are present in the initial HTML.

**3. Google Search Console**

- URL Inspection tool
- Check "Coverage" report for indexed pages
- Look for paginated URLs in index

**4. Site search**

```
site:nuxtseo.com/blog inurl:page
```

Shows indexed paginated pages.

## Checklist

::checklist{#nuxt-pagination}
- Each paginated page has its own self-referencing canonical, not one pointing to page 1
- Pagination uses real `<a href>`{lang="html"} links, not click handlers alone
- URLs use query params or path segments, never a `#` fragment
- Titles and descriptions accurately describe the sequence
- Infinite scroll and Load More expose a crawlable, paginated URL alongside the interactive UI
- robots.txt doesn't block the pagination pattern
- Keep useful pagination pages indexable; choose exclusions by content, not the presence of View All
::

## Sitemap

See the full [sitemap](https://nuxtseo.com/sitemap.md) for all pages.
