# Canonical Tags Best Practices

**Last Updated:** 2026-01-20

Comprehensive guide for implementing canonical tags correctly to prevent "Alternate page with proper canonical tag" issues in Google Search Console.

## Overview

Canonical tags tell search engines which version of a URL is the preferred (canonical) version to index. This is crucial for:

- Preventing duplicate content issues
- Consolidating ranking signals
- Reducing crawl budget waste
- Improving SEO performance

## When to Use Canonical Tags vs Redirects

### Use Canonical Tags When:

- **Functional query parameters** that change content but you want clean URL indexed:
  - `?p=monthly` / `?p=yearly` (pricing pages) → Canonicalize to `/preise`
  - `?page=2` (pagination) → Canonicalize to `/page/2/` (path-based pagination preferred)
  - `?filter=category` (filtered views) → Canonicalize to clean URL if filters don't create unique content

- **Tracking parameters** that should be indexed but consolidated:
  - Generally, **redirect instead** (see below)

### Use 301 Redirects When:

- **Tracking parameters** that should never be indexed:
  - `?source=about_page`
  - `?utm_source=...&utm_campaign=...`
  - `?trk=...`
  - `?ref=...`
  - `?gclid=...`, `?fbclid=...`
  - `?hsa_*=...`
  - `?leadSource=...`
  - `?partner=...` (unless functional for landing pages)

- **Search parameters** that create duplicate content:
  - `?search={search_term_string}` → Redirect to clean URL

- **Legacy URLs** that should redirect:
  - Old WordPress paths
  - Deprecated page structures

## Implementation Patterns

### Pattern 1: Auto-Generated Canonical (Default)

**Use for:** Most pages that don't need custom canonical URLs

```php
<?php
// head.php automatically generates canonical from REQUEST_URI (strips query params)
$aosScript = "true";
include '../base/head.php';
?>
```

**How it works:**
- `head.php` checks if `$canonical_url` is set
- If not set, generates: `https://www.ordio.com` + `parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)`
- Automatically strips query parameters

### Pattern 2: Custom Canonical URL

**Use for:** Pages that need explicit canonical URLs (blog posts, product updates, etc.)

```php
<?php
$canonical_url = "https://www.ordio.com/produkt-updates/feature-slug";
$aosScript = "true";
include '../base/head.php';
// head.php will output the canonical tag automatically
?>
```

**Important:** Don't add duplicate `<link rel="canonical">` tags after `head.php` include!

### Pattern 3: Blog Pages (Special Handling)

**Use for:** Blog posts, categories, index pages

```php
<?php
// Blog pages use blog-meta-generator.php which handles canonical URLs
// No need to set $canonical_url manually
include '../base/head.php';
?>
```

Blog meta generator ensures:
- Trailing slashes are consistent
- Pagination URLs are properly canonicalized
- Category URLs follow correct structure

## Redirect Rules in .htaccess

### Strip Tracking Parameters

```apache
# Strip tracking parameters from product pages
RewriteCond %{QUERY_STRING} ^(.*&)?(source|utm_source|utm_medium|utm_campaign|utm_term|utm_content|trk|ref|gclid|fbclid|hsa_|gad_source|leadSource|partner|signuptype)=[^&]*(&.*)?$ [NC]
RewriteCond %{REQUEST_URI} ^/(schichtplan|arbeitszeiterfassung|dokumentenmanagement|digitale-personalakte|checklisten|abwesenheiten|appstore|events|payroll|payroll-neu)$ [NC]
RewriteRule ^(.*)$ /$1? [R=301,L]
```

### Strip Search Parameters

```apache
# Strip search parameter from /tools
RewriteCond %{QUERY_STRING} ^search= [NC]
RewriteCond %{REQUEST_URI} ^/tools/?$ [NC]
RewriteRule ^tools/?$ /tools? [R=301,L]
```

### Canonicalize Functional Parameters

```apache
# Pricing page: p=monthly/yearly → clean /preise
RewriteCond %{QUERY_STRING} ^p=(monthly|yearly)$ [NC]
RewriteCond %{REQUEST_URI} ^/preise/?$ [NC]
RewriteRule ^preise/?$ /preise? [R=301,L]
```

## Common Issues and Solutions

### Issue 1: Multiple Canonical Tags

**Problem:** Page has multiple `<link rel="canonical">` tags

**Solution:**
- Set `$canonical_url` before including `head.php`
- Remove any duplicate canonical tags after `head.php` include
- `head.php` outputs canonical automatically if `$canonical_url` is set

### Issue 2: Canonical URL Contains Query Parameters

**Problem:** Canonical tag includes query parameters like `?utm_source=google`

**Solution:**
- Always use clean URLs in canonical tags (no query parameters)
- Use `parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)` to strip params
- Or set explicit clean canonical URL: `$canonical_url = "https://www.ordio.com/page"`

### Issue 3: Tracking Parameters Not Redirected

**Problem:** URLs with `?source=...` or `?utm_*=...` are indexed instead of clean URLs

**Solution:**
- Add redirect rules in `.htaccess` to strip tracking parameters
- Use 301 redirects (permanent) for SEO
- Preserve functional parameters for landing pages (`/v2`)

### Issue 4: "Alternate page with proper canonical tag" in GSC

**Problem:** Google Search Console shows many alternate pages

**Solution:**
- This is often **expected behavior** if canonical tags are correct
- If pages that should be indexed are marked as alternate, check:
  - Canonical URL points to correct page
  - Canonical URL is accessible (not 404)
  - Canonical URL is not blocked by robots.txt
  - Consider redirecting instead of canonicalizing for tracking params

## Best Practices Checklist

- [ ] **One canonical tag per page** - Set `$canonical_url` before `head.php` or let `head.php` auto-generate
- [ ] **Clean URLs only** - Canonical URLs should never contain query parameters
- [ ] **Absolute URLs** - Always use full URLs: `https://www.ordio.com/page`
- [ ] **Consistent trailing slashes** - Match site structure (blog posts use trailing slash)
- [ ] **Redirect tracking parameters** - Use 301 redirects, not just canonical tags
- [ ] **Self-referencing canonicals** - Pages should canonicalize to themselves (unless pagination/filters)
- [ ] **No circular canonicals** - Don't canonicalize A→B and B→A
- [ ] **Test with GSC** - Use URL Inspection tool to verify canonical behavior

## Testing

### Test Canonical Tags

1. **View page source** - Check for canonical tag in `<head>`
2. **Google Search Console** - Use URL Inspection tool
3. **Browser DevTools** - Check Network tab for redirects
4. **Audit script** - Run `python3 v2/scripts/dev-helpers/audit-canonical-tags.py`

### Test Redirects

1. **Browser** - Visit URL with tracking parameters, verify redirect
2. **curl** - `curl -I "https://www.ordio.com/page?utm_source=test"`
3. **Redirect checker** - Use online tools to verify 301 redirects

## Related Documentation

- [Landing Page Redirects](../../systems/landing-page-redirects/LANDING_PAGE_REDIRECTS.md) - Redirect patterns for old landing pages
- [Blog Templates](../../guides/blog-templates/BLOG_TEMPLATES.md) - Blog canonical tag handling
- [Comparison Pages](../../guides/comparison-pages/COMPARISON_PAGES_GUIDE.md) - Comparison page canonical tags

## References

- [Google: Consolidate duplicate URLs](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls)
- [Google Search Console Help: Alternate page with proper canonical tag](https://support.google.com/webmasters/answer/7440203)
