Add Year-Based Routing for Provincial and Municipal Jurisdictions #228
+3,069
−1,240
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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
/en/provincial/{province}/{year}(e.g.,/en/provincial/ontario/2023)/en/provincial/{province}(renders latest year data, no redirect)/en/provincial/{province}/{year}/departments/{department}Municipal Routes
/en/municipal/{province}/{municipality}/{year}(e.g.,/en/municipal/ontario/toronto/2024)/en/municipal/{province}/{municipality}(renders latest year data, no redirect)/en/municipal/{province}/{municipality}/{year}/departments/{department}Federal Routes (Reorganized)
/en/federal/spending(previously/en/spending)/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/budgetImportant: 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-01innext.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/ontariohasrel="canonical"pointing to/en/provincial/ontario/20245. Static Data Optimization
data/static-data.json(from SEO improvements PR) for pre-computed jurisdiction structuregetAvailableYears()andgetAvailableYearsForJurisdiction()to use static data instead of filesystem lookups for faster performancesrc/app/sitemap.tsrefactored to usejurisdictions.tsAPIs instead of readingstatic-data.jsondirectlygetFileLastModified()API for accessing file modification times from static dataCode Improvements
1. Consolidated Duplicate Pages
Created shared components to eliminate code duplication:
SpendingPageContent: Extracted fromfederal/spending/page.tsx, shared by old/spendingrouteBudgetPageContent: Extracted fromfederal/budget/page.tsx, shared by old/budgetrouteProvincialYearPageContent: Extracted fromprovincial/[province]/[year]/page.tsx, shared by non-yearprovincial/[province]/page.tsxMunicipalYearPageContent: Extracted frommunicipal/[province]/[municipality]/[year]/page.tsx, shared by non-yearmunicipal/[province]/[municipality]/page.tsxThis pattern ensures that when old routes are eventually removed, the core component logic remains intact in its intended long-term location.
2. Centralized Configuration
["en", "fr"]arrays tosrc/lib/constants.tslocalesfrom a single source3. Cleaner API & Centralized Data Access
Streamlined
src/lib/jurisdictions.ts:loadStaticData()functiongetAvailableYearsForJurisdiction()for year lookups by jurisdiction sluggetFileLastModified()for accessing file modification times from static datasrc/app/sitemap.tsto usejurisdictions.tsAPIs instead of readingstatic-data.jsondirectly4. Layout Structure
Maintained proper Next.js App Router layout hierarchy:
src/app/layout.tsxprovides root<html>and<body>tags (required by Next.js)src/app/[lang]/layout.tsxis a nested layout that wraps content with providers (Lingui, PostHog, Analytics)Files Changed
Click for details
New Files
src/app/[lang]/(main)/provincial/[province]/[year]/page.tsx- Provincial year-specific route (exportsProvincialYearPageContent)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 (exportsMunicipalYearPageContent)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/spendingusinggit mvto preserve history (exportsSpendingPageContent)src/app/[lang]/(main)/federal/budget/page.tsx- Moved from/budget(exportsBudgetPageContent)src/app/[lang]/(main)/federal/spending/layout.tsx- Layout for SEO metadata (client components can't exportgenerateMetadata)src/app/[lang]/(main)/federal/budget/layout.tsx- Layout for SEO metadatasrc/app/[lang]/(main)/tax-visualizer/layout.tsx- Layout for SEO metadatasrc/app/[lang]/(mobile)/provincial/[province]/[year]/spending-full-screen/page.tsx- Mobile full-screen route with yearsrc/app/[lang]/(mobile)/municipal/[province]/[municipality]/[year]/spending-full-screen/page.tsx- Mobile full-screen route with yearModified Files
src/lib/jurisdictions.ts- Consolidated static data loading, addedgetAvailableYearsForJurisdiction()andgetFileLastModified()APIssrc/lib/constants.ts- Centralizedlocalesarray for reuse across the codebasesrc/lib/utils.ts- Updated to importlocalesfromconstants.tssrc/app/layout.tsx- Root layout providing<html>and<body>tagssrc/app/[lang]/layout.tsx- Nested layout wrapping content with providers (no longer provides<html>/<body>)src/app/sitemap.ts- Refactored to usejurisdictions.tsAPIs instead of readingstatic-data.jsondirectlysrc/app/[lang]/(main)/spending/page.tsx- Thin wrapper importingSpendingPageContentfromfederal/spending/page.tsxsrc/app/[lang]/(main)/budget/page.tsx- Thin wrapper importingBudgetPageContentfromfederal/budget/page.tsxsrc/app/[lang]/(main)/provincial/[province]/page.tsx- Thin wrapper importingProvincialYearPageContentfrom year-specific routesrc/app/[lang]/(main)/municipal/[province]/[municipality]/page.tsx- Thin wrapper importingMunicipalYearPageContentfrom year-specific routenext.config.ts- Backward compatibility redirects (temporary, remove after 2026-03-01)localesimport fromconstants.tsDeleted Files
src/app/[lang]/(main)/[jurisdiction]/- Old conflicting routessrc/app/[lang]/(mobile)/[jurisdiction]/- Old mobile routessrc/lib/static-data.ts- Functionality merged intojurisdictions.tsData Structure
The changes leverage the existing year-based data folder structure:
URL Behaviour Reference
Old URLs → New URLs (301 Permanent Redirects)
/en/spending/en/federal/spending/en/budget/en/federal/budget/en/ontario/en/provincial/ontario/en/british-columbia/en/provincial/british-columbia/en/toronto/en/municipal/ontario/toronto/en/vancouver/en/municipal/british-columbia/vancouver/en/ontario/education/en/provincial/ontario/educationNon-Year URLs (Render Latest, No Redirect)
/en/provincial/ontario/en/provincial/ontario/2023/en/municipal/ontario/toronto/en/municipal/ontario/toronto/2024Why 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>Latest Year Rendering (200 OK, No Redirect)
/en/provincial/ontarioreturns 200 status (not 301/302)/en/provincial/ontario<link rel="canonical">points to/en/provincial/ontario/2023Year-Specific Routes
/en/provincial/ontario/2023works correctly/en/municipal/ontario/toronto/2024works correctly/en/provincial/ontario/2023/departments/educationNavigation & Links
Build & Performance
generateStaticParamspre-renders all year combinationsBenefits
✅ 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
For Developers
next.config.tsafter 2026-03-01npm run generate-statics, rebuild, and deploysrc/lib/constants.tsto add the locale, then updatelingui.config.jsand translation filessrc/lib/jurisdictions.tsAPIs - avoid direct filesystem reads in page componentsRelated Issues
Closes #[issue-number]