Fix: Missing canonical URL
Without a canonical URL, Google sees /pricing, /pricing/, /pricing?ref=twitter, and /PRICING as four separate pages with duplicate content. Search engines calculate ranking signals per exact URL string. When you omit a canonical tag, incoming link equity and behavioral metrics are split across every variation of the URL that gets crawled.
Indxel assigns canonical-url a severity weight of 10/100 — the highest single rule weight in our engine. Missing this tag forces search engines to guess which version of a page to index. They often guess wrong, selecting a parameter-bloated URL or dropping the page entirely due to duplicate content thresholds. A self-referencing canonical URL acts as a hard directive, consolidating all ranking signals to one definitive string.
How do you detect a missing canonical URL?
Run npx indxel check in your terminal to scan your local build; pages missing a canonical tag will trigger a canonical-url error.
The CLI outputs warnings in the same format as ESLint — one line per issue, with the file path, rule ID, and severity.
$ npx indxel check
Scanning 47 routes...
app/blog/[slug]/page.tsx
1:1 error Missing canonical URL canonical-url
app/pricing/page.tsx
1:1 error Missing canonical URL canonical-url
✖ 2 problems (2 errors, 0 warnings)The canonical-url rule is marked as critical. By default, Indxel will exit with code 1 if this rule fails, intentionally breaking your CI pipeline to prevent duplicate content indexing in production.
How do you fix a missing canonical URL?
Set the alternates.canonical property in your Next.js metadata export, ensuring you use a fully qualified absolute URL starting with https://.
Next.js App Router (Static Pages)
In the App Router, Next.js handles the <link rel="canonical"> tag via the Metadata API. Do not manually inject <link> tags into your JSX.
Here is the anti-pattern. Exporting title and description is insufficient.
// BAD: Missing canonical URL
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Pricing | Indxel',
description: 'Simple, transparent pricing for developer SEO.',
};
export default function PricingPage() {
return <h1>Pricing</h1>;
}Add the alternates.canonical property. You must pass the full absolute URL.
// GOOD: Explicit canonical URL
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Pricing | Indxel',
description: 'Simple, transparent pricing for developer SEO.',
alternates: {
canonical: 'https://indxel.com/pricing',
},
};
export default function PricingPage() {
return <h1>Pricing</h1>;
}Next.js App Router (Dynamic Routes)
Dynamic pages are the most common source of canonical-url failures. Developers generate the page content based on the slug, but forget to pass that slug into the metadata generator.
// BAD: Dynamic pages often forget canonical
import type { Metadata } from 'next';
export async function generateMetadata({ params }): Promise<Metadata> {
const post = await fetchPost(params.slug);
return {
title: post.title,
description: post.excerpt,
// Error: No canonical URL defined for this dynamic route
};
}Extract the slug from the route parameters and interpolate it into your base URL.
// GOOD: Dynamic canonical from params
import type { Metadata } from 'next';
type Props = {
params: { slug: string };
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await fetchPost(params.slug);
return {
title: post.title,
description: post.excerpt,
alternates: {
canonical: `https://indxel.com/blog/${params.slug}`,
},
};
}The Metadata Helper Pattern (Recommended)
Hardcoding https://indxel.com in every file is brittle. If you change domains or want to test canonicals in a staging environment, you have to execute a massive find-and-replace.
Create a constructMetadata helper function. This forces the developer to pass a canonical path, guaranteeing the canonical-url rule always passes.
// lib/metadata.ts
import type { Metadata } from 'next';
const BASE_URL = process.env.NEXT_PUBLIC_APP_URL || 'https://indxel.com';
export function constructMetadata({
title,
description,
path,
noindex = false,
}: {
title: string;
description: string;
path: string;
noindex?: boolean;
}): Metadata {
// Enforce absolute URL construction
const canonicalUrl = `${BASE_URL}${path}`;
return {
title,
description,
alternates: {
canonical: canonicalUrl,
},
robots: {
index: !noindex,
follow: !noindex,
},
};
}Usage in your page components becomes strictly typed and inherently safe:
// app/features/page.tsx
import { constructMetadata } from '@/lib/metadata';
export const metadata = constructMetadata({
title: 'Features',
description: 'Indxel platform features.',
path: '/features', // Automatically becomes https://indxel.com/features
});Plain HTML Implementation
If you are not using a framework, you must inject the canonical link directly into the <head> of your HTML document. The rel="canonical" attribute must exist before the closing </head> tag.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Pricing | Indxel</title>
<!-- BAD: Missing canonical link -->
</head><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Pricing | Indxel</title>
<!-- GOOD: Absolute canonical URL -->
<link rel="canonical" href="https://indxel.com/pricing" />
</head>How do you block missing canonical URLs from reaching production?
Add npx indxel check --ci to your build pipeline to fail the build automatically if any page is missing a canonical URL.
Running checks locally relies on developer discipline. Enforcing them in CI guarantees that no duplicate content risks ever reach production. The --ci flag strips formatting for log ingestion, and the --diff flag ensures Indxel only scans files changed in the current PR, adding less than 2 seconds to your build time.
GitHub Actions Integration
Create a workflow file to run Indxel on every pull request. If the CLI finds a canonical-url error, it exits with code 1, turning the PR check red.
# .github/workflows/seo-check.yml
name: SEO Infrastructure Check
on: [pull_request]
jobs:
indxel-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run Indxel SEO rules
run: npx indxel check --ci --diffVercel Build Command
If you bypass GitHub Actions and deploy directly via Vercel, intercept the build command.
Navigate to Project Settings > Build & Development Settings and override the Build Command:
npx indxel check --ci && next buildWhen you chain the commands with &&, a missing canonical URL will stop the Vercel build entirely. The deployment fails before Next.js even compiles, saving build minutes and protecting your search index.
What are the edge cases for canonical URLs?
Developers frequently break canonicals by using relative paths, forgetting to handle trailing slashes consistently, or hardcoding the wrong domain in staging environments.
1. Relative URLs are invalid
A canonical URL must be absolute. Passing /blog/post instead of https://example.com/blog/post is a critical error. Google will ignore relative canonicals, completely neutralizing the fix. Indxel's canonical-url rule explicitly validates that the string begins with http:// or https://.
2. Trailing slash mismatches
If your canonical URL is https://indxel.com/pricing/ but your actual page resolves to https://indxel.com/pricing, you have created a canonical mismatch. Next.js defaults to dropping trailing slashes. Ensure your alternates.canonical string matches your Next.js trailingSlash configuration in next.config.js.
3. Pagination parameters
If you have a paginated blog at /blog?page=2, the canonical URL should point to /blog?page=2, not the root /blog. If page 2 canonicalizes to page 1, Google will drop page 2 from the index entirely, and all the articles listed on page 2 will lose their crawl path.
4. Staging environments leaking into production
If you use process.env.VERCEL_URL to construct your canonicals, your production site will output canonicals like https://my-app-git-main-vercel.app/pricing. This tells Google to index your Vercel deployment URL instead of your production domain. Always define a hardcoded NEXT_PUBLIC_APP_URL environment variable for your production domain.
Which Indxel rules relate to canonical URLs?
Missing canonicals often co-occur with missing-title, missing-meta-description, and noindex-blocking errors during metadata audits.
missing-title: If a page lacks a canonical, it likely lacks a title tag. Both indicate a complete absence of SEO metadata.noindex-blocking: A page with anoindextag should generally not have a canonical URL pointing to another page. Mixingnoindexandrel="canonical"sends conflicting directives to Googlebot.og-url-mismatch: Your Open Graph URL (og:url) should always perfectly match your canonical URL. Indxel flags any divergence.
Frequently Asked Questions
What format should a canonical URL use?
Always use the full absolute URL with https://. Example: https://example.com/blog/post. Never use relative paths like /blog/post. Indxel strictly validates that canonicals are absolute https:// URLs.
Should every page have a canonical URL?
Yes. Every indexable page should have a self-referencing canonical URL. This prevents duplicate content issues from URL parameters (like UTM tags), trailing slashes, and protocol variations.
What if I have the same content on two URLs?
Point both pages' canonical to the preferred version. This tells Google to consolidate ranking signals to one URL. The non-canonical version may still be crawled by users, but it won't compete in search results.
Does a canonical URL prevent duplicate content penalties?
Yes, it consolidates duplicate content into a single indexed entity. Google does not issue manual "penalties" for standard duplicate content, but it does dilute your link equity across multiple URLs. A canonical fixes this mathematical dilution.
Can I use a canonical URL to execute a site migration?
No. Use 301 redirects for permanent migrations. Canonical tags are hints for duplicate content, while 301 redirects are strict network-level directives that permanently move the user and the search engine to a new destination.
Frequently asked questions
What format should a canonical URL use?
Always use the full absolute URL with https://. Example: https://example.com/blog/post. Never use relative paths like /blog/post. Indxel validates that canonicals are absolute https:// URLs.
Should every page have a canonical URL?
Yes. Every indexable page should have a self-referencing canonical URL. This prevents duplicate content issues from URL parameters, trailing slashes, and protocol variations.
What if I have the same content on two URLs?
Point both pages' canonical to the preferred version. This tells Google to consolidate ranking signals to one URL. The non-canonical version may still be crawled but won't compete in search results.