Skip to content

Sites

Conventions for every website an SVRBC project serves, whatever it’s built with or hosted on. Stack covers what a site is built with, and Hosting where it runs.

Every site serves /_/, an index of its special pages: pages about the site, rather than its content. They’re for the people who maintain the site, and for anyone curious how it works. Every hostname a site answers serves them (on a multi-tenant site, every tenant), and they’re marked noindex.

/_/ names the commit the site was deployed from, linked to it on GitLab (https://gitlab.com/svrbc/<project>/-/commit/<sha>), with the release’s tag when it has one. Anyone looking at the site can then tell what is live and read exactly the source behind it, without working it out from pipelines. It’s the deployed commit, not the newest on master: a merge without a release changes master but not the site.

The deploy job writes it in, not the build: the job knows the commit ($CI_COMMIT_SHA, and $CI_COMMIT_TAG for a release), and a build that contained it would differ on every commit, so the release could never reuse the build cached from master’s pipeline. The page carries a placeholder that the deploy job replaces in the built files, failing if it finds none. This handbook’s is the example: docs/_/index.md and the pages job in .gitlab-ci.yml.

/_/ starts with one page, which every site has:

  • /_/architecture, the architecture. How the site is built, deployed and served, with an SVG diagram: where the request goes (DNS, CDN, origin, any assets domain), how a change becomes a release, and links to the source repository and its infra/ records. The diagram is a file in the repository, inlined into the page so it follows the site’s light or dark theme. A merge request that changes the architecture updates the diagram too.

Others get added to /_/ as a site needs them. Prefix any new one with /_/ rather than inventing a top-level path, so it can’t collide with the site’s own pages. A site with analytics links analytics.svrbc.org from its /_/ rather than showing its numbers itself.

doreancon.org’s are the example: /_/architecture, from src/special/ and src/assets/architecture.svg.

svrbc-audit checks that each site answers /_/ and /_/architecture, and that its /_/ links the commit it was deployed from.

A site that counts its visitors uses PostHog, without cookies. No site has to have analytics, but one that does follows this, so every SVRBC site’s numbers live in one place, read the same way, and none of them needs a consent banner.

  • One PostHog project for every site, sites, in SVRBC’s organization on PostHog’s US cloud (us.posthog.com), on the free plan. PostHog records the host on every event, so a breakdown by $host splits the sites (and a multi-tenant site’s tenants), and every site uses the same key:

    posthog.init('phc_rHc6WofJM9x8MqQjHpnUrxqzJAUPbSiBuWSTrbLq3Pbn', {
    api_host: 'https://us.i.posthog.com',
    defaults: '2026-08-30',
    cookieless_mode: 'always',
    person_profiles: 'identified_only',
    })
  • One project, so the free plan. It allows one project per organization (organizations_projects, limit 1, checked 2026-10-05); its monthly allowance of events is on the billing page, beside what the sites use. A project per site would need the pay-as-you-go plan (six projects, a card on file) for nothing a $host filter doesn’t already give; considered and declined 2026-10-07. A site that ever needs its own (other people’s access, its own retention) is the time to upgrade.

  • Cookieless, always. posthog-js runs with cookieless_mode: 'always': no cookies, no local or session storage, so nothing that calls for a consent banner. PostHog tells visitors apart by a hash it computes on its servers, which resets daily, so a returning visitor counts again the next day. The project has to allow it (Settings → Web analytics → Cookieless server hash mode, or cookieless_server_hash_mode: 2 through the API); without that PostHog drops every cookieless event, silently.

  • No session replay, nor anything else that needs browser storage. Clicks are autocaptured, and pageviews follow the router (defaults: '<date>' turns on capture_pageview: 'history_change'). To name a button in the reports, give it a data-ph-capture-attribute-* attribute rather than writing posthog.capture() calls.

  • Nothing is sent from local development: the client skips initialization in dev builds, so the numbers are visitors’.

  • No secrets. The project API key (phc_…) can only send events, and is meant to be public, so it goes in the site’s committed config, like Nuxt’s runtimeConfig.public.

  • analytics.svrbc.org shows them all, in one place, from one dashboard (svrbc/analytics.svrbc.org). It counts every site together, and its comparisons break down by $host (users, sessions, pageviews; top pages by full URL), so a new site appears there by itself, with nothing to add. A site links it from its /_/; it doesn’t embed its own dashboard, and doesn’t need a /_/analytics.

  • The numbers are public. Visitor counts for SVRBC’s sites aren’t sensitive, so the dashboard is shared publicly (Share → share publicly, then the https://us.posthog.com/embedded/… link) and analytics.svrbc.org is a public, developer-facing Pages site that embeds it. Sharing is read-only, and the page needs no API key. Making them private would take more than Pages access control: a share link works for anyone who has it, so a members-only page around it would hide the link without protecting the data.

  • The dashboard is edited in PostHog, not in a repository, and shows a change at once; only its share link is in analytics.svrbc.org’s site/index.html. It started from the Website Metrics template, with tiles for pageviews by $host and autocaptured clicks by $el_text.

doreancon.org is the example of a site that counts its visitors: src/components/Analytics.astro.

One thing trips up a new project, should SVRBC ever make another:

  • A new project’s dashboards sit behind PostHog’s onboarding screen, which returns on every visit until onboarding is marked done. Skipping through it doesn’t always stick; marking it done through the API does: PATCH /api/environments/<id>/ with {"has_completed_onboarding_for": {"product_analytics": true, "web_analytics": true}}.

svrbc-audit checks that a project using posthog-js runs it cookieless and sends to the shared project, and warns about other analytics packages.