Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { withPostHogConfig } from "@posthog/nextjs-config";
import type { NextConfig } from "next";

// Where the /progress out-of-home ad URL sends people. GA4 reads utm_* off the
// landing URL and files the visit under this campaign in its acquisition
// reports; change the values here and both /progress rules follow.
const OOH_DESTINATION =
"/?utm_source=ooh&utm_medium=offline&utm_campaign=progress";

const nextConfig: NextConfig = {
async headers() {
return [
Expand Down Expand Up @@ -53,6 +59,25 @@ const nextConfig: NextConfig = {
destination: "/state-of-the-nation/:path*",
permanent: true,
},
// /progress is the vanity URL printed on an out-of-home ad — not a page.
// It carries campaign parameters to the homepage so GA4 attributes the
// visit to the ad; without them a bare redirect to / is invisible,
// indistinguishable from organic homepage traffic. Trailing slash
// included because skipTrailingSlashRedirect is on, so "/progress/"
// won't normalize itself.
//
// Temporary on purpose: a 308 would sit in a visitor's browser and skip
// the server on later visits, so repeat scans would go uncounted.
{
source: "/progress",
destination: OOH_DESTINATION,
permanent: false,
},
{
source: "/progress/",
destination: OOH_DESTINATION,
permanent: false,
},
// Election coverage lives under /vote: the index at /vote, and Toronto's
// pages at /toronto/vote/<year>. Two earlier shapes are still out in the
// world — Toronto's /toronto/elections/2026 (indexed, and the target of
Expand Down
Loading