Skip to content

Conversation

@jclarkin
Copy link
Contributor

@jclarkin jclarkin commented Dec 3, 2025

Overview

This PR introduces year-specific routing for provincial and municipal jurisdictions, allowing users to view historical budget data while maintaining backward compatibility with existing URLs. The new structure supports temporal navigation and provides a foundation for adding multi-year data as it becomes available.

New URL Structure

Provincial Routes

  • With year: /en/provincial/{province}/{year} (e.g., /en/provincial/ontario/2023)
  • Latest year: /en/provincial/{province} (renders latest year data, no redirect)
  • Department with year: /en/provincial/{province}/{year}/departments/{department}

Municipal Routes

  • With year: /en/municipal/{province}/{municipality}/{year} (e.g., /en/municipal/ontario/toronto/2024)
  • Latest year: /en/municipal/{province}/{municipality} (renders latest year data, no redirect)
  • Department with year: /en/municipal/{province}/{municipality}/{year}/departments/{department}

Federal Routes (Reorganized)

  • Spending: /en/federal/spending (previously /en/spending)
  • Budget: /en/federal/budget (previously /en/budget)

Key Features

1. Year-Specific URLs

All provincial and municipal pages now support optional year parameters in the URL path. If no year is specified, the system automatically uses the latest available year's data.

2. Backward Compatibility

Old URLs are permanently redirected (301) to the new structure to maintain existing bookmarks and external links:

  • /en/ontario/en/provincial/ontario (redirects to non-year version, not year-specific)
  • /en/toronto/en/municipal/ontario/toronto (redirects to non-year version)
  • /en/spending/en/federal/spending
  • /en/budget/en/federal/budget

Important: Old URLs redirect to the non-year versions (e.g., /en/provincial/ontario), not to year-specific URLs. This ensures bookmarked links always show the latest data.

These redirects will be removed after March 1, 2026 (marked with TODO: REMOVE AFTER 2026-03-01 in next.config.ts).

3. Latest Year Auto-Selection

URLs without a year parameter (e.g., /en/provincial/ontario) render the latest available year's data directly, without redirecting the browser. This allows users to bookmark the non-year version and always see the most recent data.

4. SEO-Friendly Canonical Links

Non-year URLs include canonical links pointing to the year-specific URL to prevent duplicate content penalties:

  • /en/provincial/ontario has rel="canonical" pointing to /en/provincial/ontario/2024
  • Search engines index the year-specific URL as the primary content

5. Static Data Optimization

  • Leverages existing data/static-data.json (from SEO improvements PR) for pre-computed jurisdiction structure
  • Updated getAvailableYears() and getAvailableYearsForJurisdiction() to use static data instead of filesystem lookups for faster performance
  • All year/department lookups now leverage cached static data from build time
  • src/app/sitemap.ts refactored to use jurisdictions.ts APIs instead of reading static-data.json directly
  • Added getFileLastModified() API for accessing file modification times from static data

Code Improvements

1. Consolidated Duplicate Pages

Created shared components to eliminate code duplication:

  • SpendingPageContent: Extracted from federal/spending/page.tsx, shared by old /spending route
  • BudgetPageContent: Extracted from federal/budget/page.tsx, shared by old /budget route
  • ProvincialYearPageContent: Extracted from provincial/[province]/[year]/page.tsx, shared by non-year provincial/[province]/page.tsx
  • MunicipalYearPageContent: Extracted from municipal/[province]/[municipality]/[year]/page.tsx, shared by non-year municipal/[province]/[municipality]/page.tsx

This pattern ensures that when old routes are eventually removed, the core component logic remains intact in its intended long-term location.

2. Centralized Configuration

  • Language codes: Moved from hardcoded ["en", "fr"] arrays to src/lib/constants.ts
  • 11 files updated to import locales from a single source
  • Makes adding new languages significantly easier

3. Cleaner API & Centralized Data Access

Streamlined src/lib/jurisdictions.ts:

  • Consolidated static data loading into a single internal loadStaticData() function
  • Added getAvailableYearsForJurisdiction() for year lookups by jurisdiction slug
  • Added getFileLastModified() for accessing file modification times from static data
  • All jurisdiction data access now goes through this centralized API
  • Refactored src/app/sitemap.ts to use jurisdictions.ts APIs instead of reading static-data.json directly
  • Page components no longer perform direct filesystem reads; all data access is abstracted

4. Layout Structure

Maintained proper Next.js App Router layout hierarchy:

  • src/app/layout.tsx provides root <html> and <body> tags (required by Next.js)
  • src/app/[lang]/layout.tsx is a nested layout that wraps content with providers (Lingui, PostHog, Analytics)
  • This structure ensures proper HTML structure while maintaining language-specific providers

Files Changed

Click for details

New Files

  • src/app/[lang]/(main)/provincial/[province]/[year]/page.tsx - Provincial year-specific route (exports ProvincialYearPageContent)
  • src/app/[lang]/(main)/provincial/[province]/page.tsx - Provincial latest-year route (thin wrapper)
  • src/app/[lang]/(main)/municipal/[province]/[municipality]/[year]/page.tsx - Municipal year-specific route (exports MunicipalYearPageContent)
  • src/app/[lang]/(main)/municipal/[province]/[municipality]/page.tsx - Municipal latest-year route (thin wrapper)
  • src/app/[lang]/(main)/federal/spending/page.tsx - Moved from /spending using git mv to preserve history (exports SpendingPageContent)
  • src/app/[lang]/(main)/federal/budget/page.tsx - Moved from /budget (exports BudgetPageContent)
  • src/app/[lang]/(main)/federal/spending/layout.tsx - Layout for SEO metadata (client components can't export generateMetadata)
  • src/app/[lang]/(main)/federal/budget/layout.tsx - Layout for SEO metadata
  • src/app/[lang]/(main)/tax-visualizer/layout.tsx - Layout for SEO metadata
  • src/app/[lang]/(mobile)/provincial/[province]/[year]/spending-full-screen/page.tsx - Mobile full-screen route with year
  • src/app/[lang]/(mobile)/municipal/[province]/[municipality]/[year]/spending-full-screen/page.tsx - Mobile full-screen route with year

Modified Files

  • src/lib/jurisdictions.ts - Consolidated static data loading, added getAvailableYearsForJurisdiction() and getFileLastModified() APIs
  • src/lib/constants.ts - Centralized locales array for reuse across the codebase
  • src/lib/utils.ts - Updated to import locales from constants.ts
  • src/app/layout.tsx - Root layout providing <html> and <body> tags
  • src/app/[lang]/layout.tsx - Nested layout wrapping content with providers (no longer provides <html>/<body>)
  • src/app/sitemap.ts - Refactored to use jurisdictions.ts APIs instead of reading static-data.json directly
  • src/app/[lang]/(main)/spending/page.tsx - Thin wrapper importing SpendingPageContent from federal/spending/page.tsx
  • src/app/[lang]/(main)/budget/page.tsx - Thin wrapper importing BudgetPageContent from federal/budget/page.tsx
  • src/app/[lang]/(main)/provincial/[province]/page.tsx - Thin wrapper importing ProvincialYearPageContent from year-specific route
  • src/app/[lang]/(main)/municipal/[province]/[municipality]/page.tsx - Thin wrapper importing MunicipalYearPageContent from year-specific route
  • next.config.ts - Backward compatibility redirects (temporary, remove after 2026-03-01)
  • Multiple page files - Replaced hardcoded language arrays with locales import from constants.ts

Deleted Files

  • src/app/[lang]/(main)/[jurisdiction]/ - Old conflicting routes
  • src/app/[lang]/(mobile)/[jurisdiction]/ - Old mobile routes
  • src/lib/static-data.ts - Functionality merged into jurisdictions.ts

Data Structure

The changes leverage the existing year-based data folder structure:

data/
├── provincial/
│   └── ontario/
│       └── 2023/
│           ├── summary.json
│           ├── sankey.json
│           └── departments/
└── municipal/
    └── ontario/
        └── toronto/
            └── 2024/
                ├── summary.json
                ├── sankey.json
                └── departments/

URL Behaviour Reference

Old URLs → New URLs (301 Permanent Redirects)

Old URL New URL Notes
/en/spending /en/federal/spending Federal spending page
/en/budget /en/federal/budget Federal budget page
/en/ontario /en/provincial/ontario Provincial jurisdiction (no year)
/en/british-columbia /en/provincial/british-columbia Provincial jurisdiction (no year)
/en/toronto /en/municipal/ontario/toronto Municipal jurisdiction (no year)
/en/vancouver /en/municipal/british-columbia/vancouver Municipal jurisdiction (no year)
/en/ontario/education /en/provincial/ontario/education Department redirect

Non-Year URLs (Render Latest, No Redirect)

URL Renders Canonical Link Behavior
/en/provincial/ontario 2023 data /en/provincial/ontario/2023 Shows latest year content without changing URL
/en/municipal/ontario/toronto 2024 data /en/municipal/ontario/toronto/2024 Shows latest year content without changing URL

Why no redirect? Users can bookmark the non-year version and always see the most recent data. The canonical link tells search engines which URL is the "official" version to prevent duplicate content penalties.

Testing Checklist

Click for details

Redirects (301 Permanent)

  • /en/spending/en/federal/spending
  • /en/budget/en/federal/budget
  • /en/<province-slug>/en/provincial/<province-slug>
  • /en/<municipality-slug>/en/municipal/<province>/<municipality-slug>
  • Old department URLs redirect correctly

Latest Year Rendering (200 OK, No Redirect)

  • /en/provincial/ontario returns 200 status (not 301/302)
  • Browser URL stays as /en/provincial/ontario
  • Page renders 2023 data (latest available)
  • <link rel="canonical"> points to /en/provincial/ontario/2023
  • Same behavior for municipal routes

Year-Specific Routes

  • /en/provincial/ontario/2023 works correctly
  • /en/municipal/ontario/toronto/2024 works correctly
  • Department pages work: /en/provincial/ontario/2023/departments/education
  • Full-screen spending pages work with year parameter

Navigation & Links

  • Main menu links to non-year versions by default
  • Internal links use appropriate URL format
  • Breadcrumbs work correctly

Build & Performance

  • Build completes successfully
  • generateStaticParams pre-renders all year combinations
  • Static data is used for faster lookups
  • No linting errors
************************************************************
URL STRUCTURE VALIDATION TEST SUITE
************************************************************

Base URL: http://localhost:3000
Time: 2025-12-05T02:13:35.428Z


============================================================
TEST 1: robots.txt
============================================================
✓ robots.txt is accessible
✓ robots.txt references sitemap.xml
Content:
User-Agent: *
Allow: /

Sitemap: https://canadaspends.com/sitemap.xml


============================================================
TEST 2: sitemap.xml
============================================================
✓ sitemap.xml is accessible
✓ sitemap.xml is well-formed XML
✓ sitemap.xml contains 38 URLs
✓ sitemap includes provincial URLs
✓ sitemap includes municipal URLs
✓ sitemap includes federal URLs

============================================================
TEST 3: Sitemap URLs (testing 38 of 38)
============================================================
✓ /en
✓ /en/federal/spending
✓ /en/federal/budget
✓ /en/tax-visualizer
✓ /en/search
✓ /en/about
✓ /en/contact
✓ /en/articles
✓ /en/articles/understanding-federal-budget-2025
✓ /fr
✓ /fr/federal/spending
✓ /fr/federal/budget
✓ /fr/tax-visualizer
✓ /fr/search
✓ /fr/about
✓ /fr/contact
✓ /fr/articles
✓ /fr/articles/understanding-federal-budget-2025
✓ /en/provincial/alberta
✓ /en/provincial/alberta/2023
✓ /en/provincial/british-columbia
✓ /en/provincial/british-columbia/2024
✓ /en/provincial/ontario
✓ /en/provincial/ontario/2023
✓ /en/municipal/british-columbia/vancouver
✓ /en/municipal/british-columbia/vancouver/2024
✓ /en/municipal/ontario/toronto
✓ /en/municipal/ontario/toronto/2024
✓ /fr/provincial/alberta
✓ /fr/provincial/alberta/2023
✓ /fr/provincial/british-columbia
✓ /fr/provincial/british-columbia/2024
✓ /fr/provincial/ontario
✓ /fr/provincial/ontario/2023
✓ /fr/municipal/british-columbia/vancouver
✓ /fr/municipal/british-columbia/vancouver/2024
✓ /fr/municipal/ontario/toronto
✓ /fr/municipal/ontario/toronto/2024

============================================================
TEST 4: 301 Redirects
============================================================
✓ /en/spending → /en/federal/spending (308)
✓ /en/budget → /en/federal/budget (308)
✓ /fr/spending → /fr/federal/spending (308)
✓ /fr/budget → /fr/federal/budget (308)
✓ /en/ontario → /en/provincial/ontario (308)
✓ /en/british-columbia → /en/provincial/british-columbia (308)
✓ /en/alberta → /en/provincial/alberta (308)
✓ /en/toronto → /en/municipal/ontario/toronto (308)
✓ /en/vancouver → /en/municipal/british-columbia/vancouver (308)

============================================================
TEST 5: Non-Year URLs (Latest Year Rendering)
============================================================
✓ /en/provincial/ontario renders without redirect (200)
✓   Canonical link points to year-specific URL: https://canadaspends.com/en/provincial/ontario/2023
✓   Has hreflang alternates (2)
✓ /en/municipal/ontario/toronto renders without redirect (200)
✓   Canonical link points to year-specific URL: https://canadaspends.com/en/municipal/ontario/toronto/2024
✓   Has hreflang alternates (2)
✓ /en/provincial/british-columbia renders without redirect (200)
✓   Canonical link points to year-specific URL: https://canadaspends.com/en/provincial/british-columbia/2024
✓   Has hreflang alternates (2)

============================================================
TEST 6: Year-Specific URLs
============================================================
✓ /en/provincial/ontario/2023 (200)
✓ /en/municipal/ontario/toronto/2024 (200)
✓ /en/provincial/british-columbia/2024 (200)

============================================================
TEST SUMMARY
============================================================
Passed:   67
Failed:   0
Warnings: 0

============================================================

✅ ALL TESTS PASSED

Benefits

Temporal Navigation - Users can browse historical budget data
Backward Compatible - Existing URLs continue to work via redirects
Bookmark-Friendly - Non-year URLs always show latest data
SEO Optimized - Canonical links prevent duplicate content issues
Performance - Static data caching improves build times
Maintainable - Shared components reduce duplication
Future-Proof - Easy to add new languages and years

Migration Notes

For Users

  • Existing bookmarks will redirect automatically
  • New bookmarks should use the non-year version for auto-updating data
  • Year-specific URLs are permanent and won't change

For Developers

  • Remove redirects from next.config.ts after 2026-03-01
  • To add a new year: Place data in the year folder, run npm run generate-statics, rebuild, and deploy
  • To add a new language: Update src/lib/constants.ts to add the locale, then update lingui.config.js and translation files
  • All jurisdiction data access should go through src/lib/jurisdictions.ts APIs - avoid direct filesystem reads in page components

Related Issues

Closes #[issue-number]

@jean-guy-batisseur
Copy link

jean-guy-batisseur bot commented Dec 3, 2025

The preview deployment for Canada Spends is ready. 🟢

Open Preview | Open Build Logs

Last updated at: 2025-12-05 02:15:20 CET

@build-canada-deploy-bot
Copy link

build-canada-deploy-bot bot commented Dec 3, 2025

Dokploy Preview Deployment

Name Status Preview Updated (UTC)
nextjs ❌ Failed Preview URL 2025-12-05T02:13:17.208Z

@jclarkin jclarkin force-pushed the clarkin/years branch 4 times, most recently from e543f6b to e1d6727 Compare December 5, 2025 01:31
- New URL structure: /provincial/{province}/{year} and /municipal/{province}/{municipality}/{year}
- Non-year URLs render latest year without redirect
- Canonical links point to year-specific URLs for SEO
- 301 redirects for old URL patterns
- Consolidated duplicate page logic into shared components
@jclarkin jclarkin marked this pull request as ready for review December 5, 2025 18:07
@xrendan xrendan self-requested a review December 5, 2025 21:38
@melkuo melkuo mentioned this pull request Jan 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant