Skip to content
Merged
Show file tree
Hide file tree
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
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,33 @@ npm run dev

Requires a `.env` file with `DATABASE_URL` (defaults to `file:./dev.db`).

## Environment variables

Required in production:

| Variable | Purpose | Notes |
|----------|---------|-------|
| `NEXT_PUBLIC_SITE_URL` | Canonical/OG/sitemap base URL | e.g. `https://buildcanada.com`. Used by `layout.tsx`, `sitemap.ts`, `robots.ts`, `lib/api/config.ts`, and JSON-LD. |
| `NEXT_PUBLIC_TRACKER_API_BASE` | Backend host for `/tracker/api/*` rewrites | Set to wherever the Outcomes Tracker API is served. Falls back to `https://www.buildcanada.com`, which will loop after cutover. |
| `YORK_FACTORY_API_URL` | York Factory CMS base URL | Defaults to `https://yorkfactory.buildcanada.com/api/v1`. Override per environment. |
| `LUMA_API_KEY` | Luma events list (`/api/events`) | Without this the homepage events list silently returns empty. |

Recommended:

| Variable | Purpose |
|----------|---------|
| `NEXT_PUBLIC_POSTHOG_TOKEN` | PostHog analytics + client-side exception capture (`error.tsx` boundaries report here). |
| `NEXT_PUBLIC_POSTHOG_HOST` | PostHog UI host. Defaults to `https://us.i.posthog.com`. |
| `NEXT_PUBLIC_GA_MEASUREMENT_ID` | Google Analytics. Loader is conditional — omit to disable. |
| `TRACKER_API_BASE` | Server-only override for tracker API base (read in `lib/tracker-api.ts` when no public var is set). |

## Deployment notes

- Set the env vars above before building. Several are baked into the static output (`NEXT_PUBLIC_*`), so a redeploy is required to change them.
- After cutover, verify `/sitemap.xml` and `/robots.txt` reference the production domain.
- Verify `/tracker` loads — if the API base is misconfigured it will silently fail to render data.
- Cloudflare-proxied projects (e.g. `/exit-tax-calculator`, `/bills`) are not served by Next; ensure their proxy rules survive any DNS / origin change.

## Design

Custom fonts: **Söhne** (headings), **Financier Text** (body), **Founders Grotesk Mono** (labels/buttons).
Expand Down
1 change: 1 addition & 0 deletions instrumentation-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_POSTHOG_TOKEN) {
api_host: "/ph",
ui_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com",
defaults: "2026-01-30",
capture_exceptions: true,
});
}
1 change: 1 addition & 0 deletions src/app/about/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const metadata: Metadata = {
title: "About",
description:
"Build Canada exists to platform the bold — individuals, ideas, and reforms — that can push our country to new frontiers.",
alternates: { canonical: "/about" },
openGraph: {
title: "About",
description:
Expand Down
1 change: 1 addition & 0 deletions src/app/builders/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { fetchBuilder, fetchBuilders } from "@/lib/api/builders";

Check warning on line 5 in src/app/builders/[slug]/page.tsx

View workflow job for this annotation

GitHub Actions / lint

'fetchBuilders' is defined but never used

export async function generateMetadata({
params,
Expand All @@ -15,6 +15,7 @@
return {
title: `${builder.name}: ${builder.tagline}`,
description: builder.quote ?? undefined,
alternates: { canonical: `/builders/${slug}` },
openGraph: {
title: `${builder.name}: ${builder.tagline} | Build Canada`,
description: builder.quote ?? undefined,
Expand Down
16 changes: 16 additions & 0 deletions src/app/builders/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ImageResponse } from "next/og";
import { BuildCanadaOGImage } from "@/lib/og-image-template";

export const size = { width: 1200, height: 630 };
export const contentType = "image/png";

export default async function Image() {
return new ImageResponse(
<BuildCanadaOGImage
title="Great Canadian Builders"
description="Short stories celebrating the incredible builders who shaped Canada."
label="Builders"
/>,
{ ...size },
);
}
1 change: 1 addition & 0 deletions src/app/builders/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const metadata: Metadata = {
title: "Great Canadian Builders",
description:
"Short stories celebrating the incredible builders who shaped Canada.",
alternates: { canonical: "/builders" },
};

export default async function BuildersPage() {
Expand Down
121 changes: 121 additions & 0 deletions src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"use client";

import { useEffect } from "react";
import posthog from "posthog-js";
import { Button } from "@/components/ui/button";

export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_POSTHOG_TOKEN) {
posthog.captureException(error, {
digest: error.digest,
pathname: window.location.pathname,
boundary: "app",
});
}
}, [error]);

return (
<div className="mx-[10px] my-[10px] border border-border-light bg-bg min-h-[calc(100vh-40px)] flex flex-col items-center justify-center relative overflow-hidden">
<div className="absolute inset-0 pointer-events-none" aria-hidden="true">
<div
className="absolute top-0 bottom-0 left-[6.7%] w-px"
style={{ backgroundColor: "var(--color-charcoal-1000)", opacity: 0.06 }}
/>
<div
className="absolute top-0 bottom-0 left-[33.3%] w-px"
style={{ backgroundColor: "var(--color-charcoal-1000)", opacity: 0.06 }}
/>
<div
className="absolute top-0 bottom-0 left-[50%] w-px"
style={{ backgroundColor: "var(--color-charcoal-1000)", opacity: 0.08 }}
/>
<div
className="absolute top-0 bottom-0 right-[33.3%] w-px"
style={{ backgroundColor: "var(--color-charcoal-1000)", opacity: 0.06 }}
/>
<div
className="absolute top-0 bottom-0 right-[6.7%] w-px"
style={{ backgroundColor: "var(--color-charcoal-1000)", opacity: 0.06 }}
/>
<div
className="absolute left-0 right-0 top-[20%] h-px"
style={{ backgroundColor: "var(--color-charcoal-1000)", opacity: 0.06 }}
/>
<div
className="absolute left-0 right-0 top-[50%] h-px"
style={{ backgroundColor: "var(--color-charcoal-1000)", opacity: 0.08 }}
/>
<div
className="absolute left-0 right-0 top-[80%] h-px"
style={{ backgroundColor: "var(--color-charcoal-1000)", opacity: 0.06 }}
/>
<div
className="absolute left-[6.7%] right-[6.7%] top-[50%] h-[2px]"
style={{ backgroundColor: "var(--color-auburn-800)", opacity: 0.25 }}
/>
</div>

<div className="relative z-10 flex flex-col items-center text-center px-5">
<span
className="block select-none"
style={{
fontFamily: "var(--font-display)",
fontWeight: 500,
fontSize: "clamp(8rem, 22vw, 16rem)",
lineHeight: 0.85,
letterSpacing: "-0.04em",
color: "var(--color-charcoal-1000)",
opacity: 0.07,
fontVariantNumeric: "tabular-nums",
}}
aria-hidden="true"
>
500
</span>

<span className="type-label text-accent mb-4 tracking-[0.2em]">
Something Broke
</span>

<h1
className="type-display-sm mb-4"
style={{ color: "var(--color-charcoal-1000)" }}
>
A beam came loose.
</h1>

<p
className="type-body-sm mb-10 max-w-[420px]"
style={{ color: "var(--color-charcoal-600)" }}
>
Something went wrong on our end. We&apos;ve been notified and are on
it. Try again, or head back to the home page.
</p>

<div className="flex items-center gap-3">
<Button as="button" onClick={reset} variant="charcoal">
Try Again
</Button>
<Button as="link" href="/" variant="ghost">
Back to Home
</Button>
</div>
</div>

<div
className="absolute bottom-4 right-5 type-mono-sm pointer-events-none"
style={{ color: "var(--color-charcoal-300)", opacity: 0.5 }}
aria-hidden="true"
>
{error.digest ? `ERR.${error.digest.slice(0, 6).toUpperCase()}` : "ERR.500"}
</div>
</div>
);
}
8 changes: 6 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@ export const metadata: Metadata = {
template: "%s | Build Canada",
},
description:
"Canada's Voice for Builders. Bold thinking from builders, reformers, and leaders pushing Canada to new frontiers.",
"Bold thinking from builders, reformers, and leaders pushing Canada to new frontiers.",
metadataBase: new URL(
process.env.NEXT_PUBLIC_SITE_URL || "https://buildcanada.com"
),
alternates: {
canonical: "/",
},
openGraph: {
type: "website",
siteName: "Build Canada",
title: "Build Canada",
description: "Canada's Voice for Builders.",
description:
"Bold thinking from builders, reformers, and leaders pushing Canada to new frontiers.",
},
twitter: {
card: "summary_large_image",
Expand Down
1 change: 1 addition & 0 deletions src/app/memos/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { fetchMemo, fetchMemos, getSiteConfig } from "@/lib/api";
import { extractHeadings } from "@/lib/extract-headings";
import { TwitterEmbed, MemoSubscribe, RelatedMemos } from "./MemoClientParts";
import { AuthorCard } from "./AuthorCard";

Check warning on line 7 in src/app/memos/[slug]/page.tsx

View workflow job for this annotation

GitHub Actions / lint

'AuthorCard' is defined but never used
import { ShareSection } from "@/components/share";
import { MemoHero } from "./MemoHero";
import { Signpost } from "@/components/custom/signpost";
Expand Down Expand Up @@ -42,6 +42,7 @@
return {
title,
description,
alternates: { canonical: `/memos/${slug}` },
openGraph: {
title,
description,
Expand Down
52 changes: 52 additions & 0 deletions src/app/memos/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"use client";

import { useEffect } from "react";
import posthog from "posthog-js";
import { Button } from "@/components/ui/button";

export default function MemosError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_POSTHOG_TOKEN) {
posthog.captureException(error, {
digest: error.digest,
pathname: window.location.pathname,
boundary: "memos",
});
}
}, [error]);

return (
<div className="mx-[10px] my-[10px] border border-border-light bg-bg min-h-[60vh] flex flex-col items-center justify-center text-center px-5">
<span className="type-label text-accent mb-4 tracking-[0.2em]">
Memos
</span>
<h1
className="type-display-sm mb-4"
style={{ color: "var(--color-charcoal-1000)" }}
>
We couldn&apos;t load this.
</h1>
<p
className="type-body-sm mb-8 max-w-[420px]"
style={{ color: "var(--color-charcoal-600)" }}
>
Something went wrong while loading the memos. Try again, or browse from
the home page.
</p>
<div className="flex items-center gap-3">
<Button as="button" onClick={reset} variant="charcoal">
Try Again
</Button>
<Button as="link" href="/" variant="ghost">
Back to Home
</Button>
</div>
</div>
);
}
1 change: 1 addition & 0 deletions src/app/memos/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const metadata: Metadata = {
title: "Memos",
description:
"Bold thinking from Canada's builders, reformers, and leaders. Read policy memos and ideas worth building on.",
alternates: { canonical: "/memos" },
openGraph: {
title: "Memos",
description:
Expand Down
3 changes: 1 addition & 2 deletions src/app/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ export const contentType = "image/png";
export default async function Image() {
return new ImageResponse(
<BuildCanadaOGImage
title="Canada's Voice for Builders"
title="Build Canada"
description="Bold thinking from builders, reformers, and leaders pushing Canada to new frontiers."
label="Homepage"
/>,
{ ...size }
);
Expand Down
10 changes: 6 additions & 4 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@
);
}

function SocialLinks() {

Check warning on line 159 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / lint

'SocialLinks' is defined but never used
return (
<div className="pt-3 pb-8">
<div className="max-w-[1080px] mx-auto flex items-center gap-2 flex-wrap">
Expand All @@ -178,10 +178,12 @@
/>
</a>
))}
<div className="w-px h-[18px] bg-border-light mx-0.5" />
<LinkButton href="/content" variant="primary">
Full Archive
</LinkButton>
{/* /content is being phased out — hide entry point until decision is finalized.
<div className="w-px h-[18px] bg-border-light mx-0.5" />
<LinkButton href="/content" variant="primary">
Full Archive
</LinkButton>
*/}
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/app/projects/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const metadata: Metadata = {
title: "Projects",
description:
"Transparent government data and better tools for pro-growth voices.",
alternates: { canonical: "/projects" },
openGraph: {
title: "Projects",
description:
Expand Down
1 change: 1 addition & 0 deletions src/app/toronto/memos/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export async function generateMetadata({
return {
title,
description,
alternates: { canonical: `${BASE_PATH}/${slug}` },
openGraph: {
title,
description,
Expand Down
1 change: 1 addition & 0 deletions src/app/toronto/memos/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const metadata: Metadata = {
title: "Memos",
description:
"Bold thinking for Toronto. Read policy memos and ideas worth building on.",
alternates: { canonical: "/toronto/memos" },
openGraph: {
title: "🏗️ Toronto — Memos",
description: "Bold thinking for Toronto.",
Expand Down
15 changes: 15 additions & 0 deletions src/app/tracker/commitments/[id]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Metadata } from "next";

export const metadata: Metadata = {
title: "Commitment - Outcomes Tracker - Build Canada",
description:
"Detailed status, sources, and assessments for a tracked federal commitment.",
};

export default function CommitmentDetailLayout({
children,
}: {
children: React.ReactNode;
}) {
return children;
}
16 changes: 16 additions & 0 deletions src/app/tracker/commitments/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Metadata } from "next";

export const metadata: Metadata = {
title: "Commitments - Outcomes Tracker - Build Canada",
description:
"Browse and search every tracked commitment from Canada's federal government, with status and progress updates.",
alternates: { canonical: "/tracker/commitments" },
};

export default function CommitmentsLayout({
children,
}: {
children: React.ReactNode;
}) {
return children;
}
Loading
Loading