---
title: "Pagination SEO in Vue · Nuxt SEO"
canonical_url: "https://nuxtseo.com/learn-seo/vue/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 Vue 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 Vue with self-referencing canonicals and crawlable links."
  "og:title": "Pagination SEO in Vue · Nuxt SEO"
---

Nuxt SEO on GitHub

# **Pagination SEO in Vue**

Google indexes page 1 and buries the rest when canonicals point the wrong way. Fix pagination in Vue 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">
import { useHead } from '@unhead/vue'
import { useRoute } from 'vue-router'

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

```ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/blog',
      component: BlogList,
      // Handles /blog?page=2
    }
  ]
})
```

**Path segment approach:**

```ts
const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/blog',
      component: BlogList
    },
    {
      path: '/blog/page/:page',
      component: BlogList
    }
  ]
})
```

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. Navigation rendered only after JavaScript runs may not be crawled.

**SSR/SSG approach (recommended):**

```vue
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()
const currentPage = computed(() => Number(route.query.page) || 1)
const totalPages = 10

function goToPage(page: number) {
  navigateTo({ query: { page } })
}
</script>

<template>
  <nav>
    <!-- Crawlable links with href -->
    <a
      v-for="n in totalPages"
      :key="n"
      :href="`/blog?page=${n}`"
      :class="{ active: n === currentPage }"
      @click.prevent="goToPage(n)"
    >
      {{ n }}
    </a>
  </nav>
</template>
```

The `**href**` attribute makes links crawlable. `**@click.prevent**` enables client-side navigation for users.

**SPA approach (requires prerendering):**

If using a client-only SPA, you need [**~~prerendering~~**](https://nuxtseo.com/learn-seo/vue/spa/prerendering) or SSR for Google to discover pagination links. Pure SPAs without prerendering won't get pages 2+ indexed.

## Pagination Component

Full example with prev/next links and numbered pages:

```vue
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

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
)

function goToPage(page: number) {
  navigateTo({ query: { ...route.query, page } })
}
</script>

<template>
  <nav aria-label="Pagination">
    <!-- Previous link -->
    <a
      v-if="prevPage"
      :href="`/blog?page=${prevPage}`"
      @click.prevent="goToPage(prevPage)"
    >
      Previous
    </a>

    <!-- Page numbers -->
    <a
      v-for="n in totalPages"
      :key="n"
      :href="`/blog?page=${n}`"
      :aria-current="n === currentPage ? 'page' : undefined"
      @click.prevent="goToPage(n)"
    >
      {{ n }}
    </a>

    <!-- Next link -->
    <a
      v-if="nextPage"
      :href="`/blog?page=${nextPage}`"
      @click.prevent="goToPage(nextPage)"
    >
      Next
    </a>
  </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">
import { useHead } from '@unhead/vue'
import { useRoute } from 'vue-router'

const route = useRoute()
const showAll = route.query.show === 'all'

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

<template>
  <div>
    <a href="/blog?show=all">View All</a>

    <!-- 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">
import { onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()
const posts = ref([])
const page = ref(Number(route.query.page) || 1)

async function loadMore() {
  page.value++
  const newPosts = await fetchPosts(page.value)
  posts.value.push(...newPosts)

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

onMounted(() => {
  (async () => {
    posts.value = await fetchPosts(page.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">
      <a :href="`/blog?page=${page + 1}`">Next Page</a>
    </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

```ts
import express from 'express'

const app = express()

app.get('/api/posts', (req, res) => {
  const page = Number(req.query.page) || 1
  const limit = 10
  const offset = (page - 1) * limit

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

  res.json({
    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">
import { useHead } from '@unhead/vue'
import { useRoute } from 'vue-router'

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 Vue SEO.'
        : `Blog posts page ${page}. Read more about Vue 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/vue/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/vue/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=vue**`)
- 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
<script setup lang="ts">
import { useHead } from '@unhead/vue'

// Don't do this - tells Google pages 2+ are duplicates
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
<template>
  <!-- Not crawlable - Googlebot can't click buttons -->
  <button @click="nextPage">
    Next
  </button>
</template>
```

**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/vue/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. If pagination appears only in JavaScript, add SSR or prerendering.

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

[**~~Nuxt SEO~~**](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction) and the [**~~Nuxt pagination guide~~**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/pagination) walk through the same fixes with Nuxt's built-in composables and file-based routing.

[**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/vue/controlling-crawlers/canonical-urls)

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

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

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

[**URL Structure** Vue Router gives you no free URLs. Get slugs, casing, and query params right by hand before duplicates hurt your rankings.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/url-structure) [**Trailing Slashes** Fix duplicate content from /about vs /about/ with Vue Router strict mode, edge redirects, and matching canonical tags.](https://nuxtseo.com/learn-seo/vue/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)