Programmatic SEO with Vue
- Programmatic pages need useful differentiated content, reliable data, and a clear purpose for the reader
- Google's scaled content abuse policy targets ranking manipulation without user value, regardless of the generation method
- Vue Router matches paths; your data, rendering, and hosting layers still provide content, metadata, and missing-resource responses
- Sitemaps list URLs. Internal links and a verified rendering pipeline serve separate discovery and delivery tasks
Programmatic SEO uses structured data and templates to create pages. A feature directory, a comparison collection, or a location catalog can fit this approach. The number of pages does not establish quality, demand, or protection from a spam action.
Build a page because its content helps an intended visitor. Avoid turning a large keyword list into interchangeable pages with different names.
What Programmatic SEO Requires
Start with a purpose, a dependable data source, and content that answers the page's specific question. Search research can inform demand, but a page can also serve existing users without a high measured search volume.
Define the publication boundary: which records qualify for a page, which URLs identify them, how updates reach the page, and what happens when a record disappears. Do not let an arbitrary slug create a success response containing filler.
The Quality Line After Google's March 2024 Update
Google's March 2024 announcement introduced expanded scaled content abuse policies and described changes to core ranking systems. It does not establish a fixed page-count or word-count threshold for programmatic content.
What Google Filters
Google's people-first content guidance asks creators to provide useful, reliable content. Compare neighboring pages: do their differences help the intended visitor, or merely swap names?
| Review question | Inspect |
|---|---|
| Does this page answer a distinct need? | Its purpose, audience, and actual content |
| Can the reader trust the data? | Original sources, dates, units, and corrections |
| Does the template fit the record? | Missing fields, inappropriate claims, and misleading headings |
| Can visitors reach the page? | Links, route output, and direct responses |
These are editorial checks, not Google's published numerical ranking formula.
AI Content and Scaled Abuse
Scaled content abuse concerns large amounts of content created primarily to manipulate rankings without helping users. Google covers automated, human, and combined methods. AI-written variations do not prove that the pages add value; automation alone does not prove a violation.
Page Types That Work
These page types can organize useful information. None guarantees traffic, rankings, or a conversion rate.
Feature Pages
A feature page can explain an actual capability, its limits, and relevant examples. Use data that describes what the feature does. The illustrative Vue example below renders a small collection of guide topics; it does not claim a real product has these capabilities.
Comparison Pages
Show verifiable differences, the comparison date, and conditions that matter to the reader. Avoid invented benchmarks or automatic claims that your product is best. If two orderings contain the same comparison, choose a preferred URL; different editorial perspectives may deserve separate identities.
Alternative Pages
Describe why an alternative fits particular needs and where it differs. A competitor name or estimated CPC does not prove commercial demand for your page. Keep prices and feature claims tied to current primary evidence when publishing them.
Category Pages
A category can introduce related pages and explain how to choose between them. Link to actual published counterparts. A hub is useful navigation, rather than proof that every child will rank.
Vue Implementation
The following bounded example uses an in-memory guide collection and a complete Vue component. Install Vue Router and Unhead, then configure their plugins using the Vue setup guide. This example supplies data and component output; your SSR, generation, and host integration still serve the URLs.
Data Source
Save this as features.ts. These three illustrative records contain different guidance and limitations:
export interface Feature {
slug: string
title: string
description: string
details: string[]
tags: string[]
}
export const features = [
{
slug: 'color-guide',
title: 'Color guide',
description: 'Record a page palette and check its contrast.',
details: [
'Pair each color with the surface where it appears.',
'A palette alone does not prove accessible text contrast.'
],
tags: ['design', 'color']
},
{
slug: 'font-guide',
title: 'Font guide',
description: 'Inspect typography and fallback behavior.',
details: [
'Check the rendered font, weight, and fallback on representative text.',
'A CSS font-family declaration does not prove which font rendered.'
],
tags: ['design', 'type']
},
{
slug: 'layout-guide',
title: 'Layout guide',
description: 'Inspect responsive layout at useful viewport sizes.',
details: [
'Check content order and horizontal overflow on a narrow viewport.',
'One desktop screenshot does not prove mobile usability.'
],
tags: ['design', 'layout']
}
] satisfies Feature[]
export function findFeature(slug: string): Feature | undefined {
return features.find(feature => feature.slug === slug)
}
export function relatedFeatures(feature: Feature): Feature[] {
return features.filter(other => other.slug !== feature.slug
&& other.tags.some(tag => feature.tags.includes(tag)))
}A real loader can use a database or CMS. Parse its response at the boundary, distinguish a missing record from an infrastructure failure, and review the data before publication. The in-memory example does not validate arbitrary external input.
Page Component Pattern
Save this as FeaturePage.vue beside features.ts. Its prop contains the current record or null for a confirmed missing record. The head getters follow prop changes:
<script setup lang="ts">
import type { Feature } from './features'
import { useHead, useSeoMeta } from '@unhead/vue'
import { computed } from 'vue'
import { RouterLink } from 'vue-router'
import { relatedFeatures } from './features'
const props = defineProps<{ feature: Feature | null }>()
const related = computed(() => props.feature ? relatedFeatures(props.feature) : [])
useSeoMeta({
title: () => props.feature?.title ?? 'Guide not found',
description: () => props.feature?.description,
robots: () => props.feature ? 'index, follow' : 'noindex, follow'
})
useHead(() => ({
link: props.feature
? [{ rel: 'canonical', href: `https://example.com/features/${props.feature.slug}` }]
: []
}))
</script>
<template>
<article v-if="props.feature">
<RouterLink to="/features">
All guides
</RouterLink>
<h1>{{ props.feature.title }}</h1>
<p>{{ props.feature.description }}</p>
<p v-for="detail in props.feature.details" :key="detail">
{{ detail }}
</p>
<nav aria-label="Related guides">
<ul>
<li v-for="other in related" :key="other.slug">
<RouterLink :to="`/features/${other.slug}`">
{{ other.title }}
</RouterLink>
</li>
</ul>
</nav>
</article>
<main v-else>
<h1>Guide not found</h1>
</main>
</template>In an installed router, this excerpt selects the current record. It uses the preloaded FeatureHub component from the internal linking guide:
import type { RouteRecordRaw } from 'vue-router'
import FeatureHub from './FeatureHub.vue'
import FeaturePage from './FeaturePage.vue'
import { features, findFeature } from './features'
const routes: RouteRecordRaw[] = [
{
path: '/features',
component: FeatureHub,
props: {
features: features.map(feature => ({
path: `/features/${feature.slug}`,
title: feature.title,
description: feature.description
}))
}
},
{
path: '/features/:slug',
component: FeaturePage,
props: route => ({
feature: typeof route.params.slug === 'string'
? findFeature(route.params.slug) ?? null
: null
})
}
]A router match does not establish that a record exists. Your host or server must return 404 for missing content; the component cannot set that status. The 404 guide demonstrates the HTTP boundary. The Vite SSR guide covers fresh app/router/head instances and serialized output.
Use structured data only when it describes the actual page and meets a supported feature's requirements. A feature description is not automatically a SoftwareApplication rich-result candidate. See the Schema.org guide for installation and policy boundaries.
Generating Routes for Sitemap
A sitemap lists published URLs. It does not create route content or prerender pages. This example assumes an installed Vite app and the data file above. Install vite-plugin-sitemap:
pnpm add -D vite-plugin-sitemap@0.8.2Save this as vite.config.ts, with features.ts beside it. Use your existing Vue entry and HTML document:
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import Sitemap from 'vite-plugin-sitemap'
import { features } from './features'
export default defineConfig({
plugins: [
vue(),
Sitemap({
hostname: 'https://example.com',
generateRobotsTxt: false,
lastmod: new Date('2026-03-23T00:00:00Z'),
dynamicRoutes: ['/features', ...features.map(feature => `/features/${feature.slug}`)]
})
]
})This illustrative collection assumes every listed page last received a significant content change on March 23, 2026. Replace that date with real modification timestamps, using the plugin's per-route lastmod map when pages have different dates. Do not substitute the build time. This configuration disables robots file generation; configure your site's robots policy separately.
Build the app and inspect the generated XML. Confirm each expected canonical URL, its modification date, and the page it serves. This plugin also scans built HTML files, so review those additional entries.
If you choose generation, configure concrete URLs through the prerendering guide. Registering /features/:slug alone does not enumerate every guide, and a sitemap does not replace a generation route list.
Managing Crawl Budget at Scale
Google's crawl-budget guidance concerns large or rapidly changing sites and sites with many discovered but unindexed URLs. Its counts are rough examples, not a 1,000-page threshold.
Avoid unneeded crawlable variants and keep useful pages reachable. Canonical tags do not prevent crawling; robots restrictions prevent Google from fetching page-level annotations. Keep lastmod values tied to significant content changes, and choose a crawl or indexing policy for actual URL classes rather than blocking every parameter.
The Zero-Click and AI Search Factor
A forecast about future search volume is not a measured outcome for your site. Check your own impressions, clicks, visits, and useful actions when assessing the collection.
For Google's AI features, existing SEO practices apply. Special AI files or schema do not guarantee inclusion. Useful content and accurate metadata do not promise citations by Google or other providers.
Avoiding Thin Content
Google does not prescribe a preferred word count. A 300-word rule cannot establish page quality.
Inspect whether the page answers its intended question with accurate, relevant information. A template can organize that information, but a name swap does not supply a meaningful difference. Do not add filler solely to meet a numerical threshold.
Internal Linking for pSEO
Link published pages where the destination helps a visitor. The component above links to its hub and related guides. A sitemap and internal links provide discovery paths without guaranteeing indexing.
Link Topology
A hub can lead to guide pages. Guides can refer readers to relevant siblings and back to the collection. Do not require every page to link to every other page or infer a fixed authority gain from a graph shape.
Automated Related Links
The exported relatedFeatures() function selects other records with a shared tag. In the illustrative dataset, each guide has two related guides. When adding records, review the resulting links rather than assuming a shared tag always establishes a useful relationship.
Common Mistakes
Generating Thousands of Near-Identical Pages
Large-scale ranking manipulation without user value can violate Google's spam policies. Page count alone does not prove abuse or a domain-wide penalty. Publish useful records and review the resulting collection before expanding it.
Missing Canonical Tags on URL Variations
If two URLs serve duplicate or near-identical comparison content, choose and publish a preferred URL. Do not alphabetize arbitrary slugs as a universal canonical rule. Confirm that the preferred URL exists and describes the same comparison.
A browser navigation guard cannot redirect the HTTP response already sent. If redirects form part of your policy, configure the host or server boundary and verify it.
Not Submitting Generated URLs to Sitemap
Add eligible published URLs to your sitemap and link them from useful pages. Sitemaps are discovery hints; submitting a URL does not guarantee crawling or indexing. Verify that entries correspond to actual pages.
No Internal Links Between Generated Pages
A route pattern does not create navigation. Compare your link graph with the known page inventory and add relevant discovery links. Read the internal linking guide for rendered anchors and orphan detection.
Keyword-Stuffed URLs
Choose readable, stable identifiers. Do not promise a click-through increase from a short slug or prescribe a universal keyword count. See the URL structure guide for current Google guidance.
Checklist
- Each page serves a clear reader need with reliable differentiated content
- Current record data drives metadata and the visible page
- Confirmed missing records receive a real missing-resource status through the host or server
- Related links and hubs reach the intended published pages
- Sitemap entries and any generation route list match real canonical URLs
- Canonical and crawl policies preserve meaningful content differences
- Claims, dates, and structured data match the page rather than a generic template
Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.