---
title: "Pagination SEO in Nuxt · Nuxt SEO"
canonical_url: "https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/pagination"
last_updated: "2026-07-16T12:00:00.000Z"
meta:
  author: "Harlan Wilton"
  description: "Google indexes page 1 and buries the rest when canonicals point the wrong way. Fix pagination in Nuxt with self-referencing canonicals and crawlable links."
  "og:description": "Google indexes page 1 and buries the rest when canonicals point the wrong way. Fix pagination in Nuxt with self-referencing canonicals and crawlable links."
  "og:title": "Pagination SEO in Nuxt · Nuxt SEO"
---

Nuxt SEO on GitHub

# **Pagination SEO in Nuxt**

Google indexes page 1 and buries the rest when canonicals point the wrong way. Fix pagination in Nuxt with self-referencing canonicals and crawlable links.

[Harlan Wilton](https://x.com/harlan-zw)14 mins read Published **Dec 17, 2025** Updated **Jul 16, 2026**

**What you'll learn**

- 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>**` 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. Search engines treat each page as separate: set canonical tags wrong and Google indexes page 1 only, set them right and every page ranks.

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>**` links instead.

## Self-Referencing Canonical Tags

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

![Pagination Canonical Flow](https://nuxtseo.com/images/learn-seo/vue/pagination-canonical-flow.svg)

**Don't** point all pages to page 1. That tells Google only page 1 matters, hiding pages 2+ from search results.

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

useHead({
  link: [
    {
      rel: 'canonical',
      href: `https://nuxtseo.com/blog?page=${page}`
    }
  ]
})
</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:

pages/blog.vue

```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:

pages/blog/index.vue

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

pages/blog/page/\[page].vue

```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>**` 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>**` 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>**` tags. Googlebot follows these to discover your content.

## View All Page Approach

Offer a single page with all content. Point canonicals from paginated pages to the View All page.

```vue
<script setup lang="ts">
const route = useRoute()
const showAll = route.query.show === 'all'

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

<template>
  <div>
    <NuxtLink to="/blog?show=all">
      View All
    </NuxtLink>

    <!-- Paginated content -->
    <article v-for="post in paginatedPosts" :key="post.id">
      {{ post.title }}
    </article>
  </div>
</template>
```

**Drawbacks:**

- Slow page load with 100+ items
- Poor mobile experience
- Images kill performance

A single page trades pagination's speed for simplicity. Google's [**~~own guidance~~**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading) notes a single-page approach can't handle large result counts as well as paginating, and costs more on mobile networks. Use it for small datasets; for large catalogs, stick with self-referencing canonicals.

## Infinite Scroll vs Pagination

![Pagination vs Infinite Scroll Decision](https://nuxtseo.com/images/learn-seo/vue/pagination-vs-infinite-scroll.svg)

| **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.

```vue
<script setup lang="ts">
const route = useRoute()
const router = useRouter()
const posts = ref([])
const page = ref(Number(route.query.page) || 1)

async function loadMore() {
  page.value++
  const { data: newPosts } = await useFetch(`/api/posts?page=${page.value}`)
  posts.value.push(...newPosts.value)

  // Update URL for crawlers
  await navigateTo({ query: { page: page.value } }, { replace: true })
}

const { data: initialPosts } = await useFetch(`/api/posts?page=${page.value}`)
posts.value = initialPosts.value
</script>

<template>
  <div>
    <article v-for="post in posts" :key="post.id">
      {{ post.title }}
    </article>

    <button @click="loadMore">
      Load More
    </button>

    <!-- Crawlable pagination links -->
    <nav class="sr-only">
      <NuxtLink :to="{ query: { page: page + 1 } }">
        Next Page
      </NuxtLink>
    </nav>
  </div>
</template>
```

**Load More button:**

Works for UX, but [**~~Googlebot can't click buttons~~**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading). Use the hybrid approach above if SEO matters.

## Server-Side Pagination

Nuxt Content provides built-in pagination support:

pages/blog.vue

```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:

server/api/posts.get.ts

```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 = Number(route.query.page) || 1

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

Give each page its own title and description. Without one, Google may flag them as [**~~duplicate content~~**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content) or pick the wrong page to rank; see [**~~Search Engine Journal's pagination guide~~**](https://www.searchenginejournal.com/technical-seo/pagination/) for title formulas that keep page 1 the primary result.

## Noindex on Paginated Pages

**Don't** noindex paginated pages. [**~~This loses indexed content~~**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexing-issues). Google stops crawling noindexed pages, hiding your products or articles.

Only noindex if:

- Filter/sort variations create infinite URLs ( `**/blog?sort=date&order=asc&filter=nuxt**`)
- You have a View All page as canonical
- Pages have no unique content

For most sites, [**~~keep paginated pages indexable~~**](https://ahrefs.com/blog/rel-prev-next-pagination/).

## 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 tells Google pages 2+ are duplicates. Use self-referencing canonicals.

**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 disallow hides every page past page 1 from Google entirely, including the self-referencing canonicals you set up. 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>**` 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**

- Each paginated page has its own self-referencing canonical, not one pointing to page 1
- Pagination uses real `**<a href>**` links, not click handlers alone
- URLs use query params or path segments, never a `**#**` fragment
- Titles and descriptions differ per page
- Infinite scroll and Load More expose a crawlable, paginated URL alongside the interactive UI
- robots.txt doesn't block the pagination pattern
- Paginated pages stay indexable unless they're filter/sort noise or a View All page exists

[**The 2026 SEO Checklist for Nuxt & Vue ** Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.](https://nuxtseo.com/learn-seo/checklist) [Haven't launched yet? Start with the **Pre-Launch Warmup**](https://nuxtseo.com/learn-seo/pre-launch-warmup)

---

### **Related **

[**Canonical URLs**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls)

[**URL Structure**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/url-structure)

[**Trailing Slashes**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes)

[**Nuxt SEO Utils**](https://nuxtseo.com/docs/seo-utils/getting-started/introduction)

[**Query Parameters**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/query-parameters)

[**URL Structure** Get slugs, casing, and query params right so file-based routing produces URLs Google indexes cleanly instead of as duplicates.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/url-structure) [**Trailing Slashes** Learn when to enable trailing slashes, how to configure NuxtLink, and the one-line Nuxt SEO setup.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes)

**On this page**

- [Self-Referencing Canonical Tags](#self-referencing-canonical-tags)
- [Pagination URL Structure](#pagination-url-structure)
- [Crawlable Links](#crawlable-links)
- [Pagination Component](#pagination-component)
- [View All Page Approach](#view-all-page-approach)
- [Infinite Scroll vs Pagination](#infinite-scroll-vs-pagination)
- [Server-Side Pagination](#server-side-pagination)
- [Meta Titles and Descriptions](#meta-titles-and-descriptions)
- [Noindex on Paginated Pages](#noindex-on-paginated-pages)
- [Common Mistakes](#common-mistakes)
- [Testing Pagination SEO](#testing-pagination-seo)
- [Checklist](#checklist)