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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ PUBLIC_SANITY_STRICT_MODE=false
# Optional. Used by `<meta property="fb:app_id">` in BaseLayout.
PUBLIC_FB_APP_ID=

# ---- Google Analytics (gtag.js / GA4) --------------------------------------
# Optional. When set, BaseLayout loads gtag and tracks page views
# (including Astro ClientRouter navigations).
# Example: PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXX
PUBLIC_GA_MEASUREMENT_ID=

# ---- Deploy trigger (server-only) -----------------------------------------
# Used by /deploy and /api/trigger-deploy in local dev + Vercel.
# Do NOT prefix with PUBLIC_. Keep these secret.
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Release history: [CHANGELOG.md](CHANGELOG.md).
- **Three.js background** — Particle field and wireframe accents (respects reduced motion; pause control)
- **Design system** — Scoped component styles plus tokens in `src/styles/global.css` (self-hosted **Tiny5** + **Nunito**)
- **Configurable imagery** — Header brand, org logos, footer sponsors (`src/data/siteLogos.ts`)
- **Analytics** — [Vercel Analytics](https://vercel.com/docs/analytics) (`@vercel/analytics`)
- **Analytics** — [Vercel Analytics](https://vercel.com/docs/analytics) (`@vercel/analytics`); optional GA4 via `PUBLIC_GA_MEASUREMENT_ID`
- **TypeScript**, sitemap, client-side transitions (`ClientRouter`)

## Requirements
Expand Down Expand Up @@ -56,9 +56,11 @@ npm run preview
PUBLIC_SANITY_PROJECT_ID=your_project_id
PUBLIC_SANITY_DATASET=production
PUBLIC_SANITY_STRICT_MODE=false
PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXX
```

- Set `PUBLIC_SANITY_STRICT_MODE=true` in production to fail builds when Sanity fetches fail instead of silently shipping empty fallback content.
- Set `PUBLIC_GA_MEASUREMENT_ID` to enable Google Analytics 4 (gtag). Leave unset to skip loading the tag.

**Standalone Studio** (`studio-hack-michigan-/`) resolves project and dataset in `sanity.env.ts`:

Expand All @@ -70,6 +72,8 @@ The Sanity CLI loads `.env` from `studio-hack-michigan-/` when you run `sanity d

For Facebook Open Graph sharing you can set `PUBLIC_FB_APP_ID` (see `src/env.d.ts`).

For Google Analytics 4, set `PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXX`. When present, `BaseLayout` loads gtag.js and records page views on first load and on Astro `ClientRouter` navigations.

### Deploy trigger env

The internal `/deploy/` page posts to `/api/trigger-deploy/`, which forwards to an n8n webhook.
Expand Down
2 changes: 2 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ declare module "*.astro" {
interface ImportMetaEnv {
/** Facebook App ID for `fb:app_id` (Open Graph). Set in Vercel / `.env`. */
readonly PUBLIC_FB_APP_ID?: string;
/** GA4 Measurement ID (e.g. `G-XXXXXXXX`). When set, BaseLayout injects gtag. */
readonly PUBLIC_GA_MEASUREMENT_ID?: string;
/** Sanity project ID. Defaults to the HackMI project in `astro.config.mjs` if unset. */
readonly PUBLIC_SANITY_PROJECT_ID?: string;
/** Sanity dataset. Defaults to `production` in `astro.config.mjs` if unset. */
Expand Down
39 changes: 39 additions & 0 deletions src/layouts/BaseLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,33 @@ const {
} = Astro.props;

const fbAppId = import.meta.env.PUBLIC_FB_APP_ID?.trim();
const gaMeasurementId = import.meta.env.PUBLIC_GA_MEASUREMENT_ID?.trim();

/**
* Inline gtag bootstrap as a string so prettier-plugin-astro does not parse
* the script body (define:vars + modern JS has triggered SyntaxError on check).
*/
const gtagBootstrap = gaMeasurementId
? `
window.dataLayer = window.dataLayer || [];
function gtag() {
window.dataLayer.push(arguments);
}
if (!window.__hackmiGtagInit) {
window.__hackmiGtagInit = true;
gtag("js", new Date());
gtag("config", ${JSON.stringify(gaMeasurementId)}, { send_page_view: false });
function sendGaPageView() {
gtag("event", "page_view", {
page_title: document.title,
page_location: location.href,
page_path: location.pathname + location.search + location.hash,
});
}
document.addEventListener("astro:page-load", sendGaPageView);
}
`.trim()
: "";

const defaultOgPath = `${base}social-card.jpg`;
const image = imageProp ?? defaultOgPath;
Expand Down Expand Up @@ -151,6 +178,18 @@ const selfHostedFontFaces = `
/>
<title>{title}</title>
<ClientRouter />

{
gaMeasurementId && (
<>
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${gaMeasurementId}`}
/>
<script is:inline set:html={gtagBootstrap} />
</>
)
}
</head>
<body>
<a class="skip-link" href="#main-content">Skip to main content</a>
Expand Down
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "astro",
"ignoreDeprecations": "5.0",
"ignoreDeprecations": "6.0",
"baseUrl": ".",
"paths": {
"~/*": ["src/*"]
"~/*": ["./src/*"]
}
},
"exclude": ["dist", "studio-hack-michigan-"]
Expand Down
Loading