Tech stack
We built kmceagles.org as a static site with a hand-written front end and free services, so the club pays nothing beyond the domain and we track no visitors.
Principles
- Zero recurring cost
Every service is on a free tier and none is priced per user. We pay for the domain and nothing else.
- Privacy first
No tracking and no consent banner. Two cookies: one keeps players logged in to answer for practices and games, the other remembers a language a visitor picks. Translation loads only when asked.
- No frameworks
Hand-written HTML, CSS custom properties and plain JavaScript. Every page can be read with JavaScript switched off; the maps, search and the attendance tools need it.
- Editable by volunteers
All content is YAML and Markdown, edited in a browser through Pages CMS and versioned in Git.
The stack, layer by layer
-
1Content
- YAML and Markdown One file per player, team, roster, event, rink, gallery and post; relationships resolved at build time.
- Pages CMS Browser editor with plain-language forms that commits straight to GitHub. Admins are invited by email. Free, no seats.
-
2Build
- Eleventy 3 Static site generator with Nunjucks templates.
- eleventy-img and sharp AVIF, WebP and JPEG at three widths, lazy-loaded with fixed dimensions, plus a share image for every page in the club's typeface.
- resvg-js Renders player trading cards, game-score images and the link preview of every event page from SVG.
- Pagefind Static search index built after every build; searches run in the browser.
-
3Delivery
- Cloudflare Pages Deploys from the main branch on every commit, with security headers, redirects from the old site and a global cache.
- GitHub Actions Rebuilds the site every night, so the next event and past games roll over on days without edits. A second workflow asks the site every hour to send the attendance reminders that are due, as a backup to a small Cloudflare Worker that asks every five minutes.
- Pages Functions Small functions relay the contact form after a spam check and run website attendance: signing in (passkeys checked with the browser's own cryptography, codes by email through Resend, Google and GitHub), In or Out answers, guests, subs, line-ups, the history, People, the reminder run, and shared links for the team chat (/s/<token>/, with a preview.jpg picture for the chat's preview). No key ever reaches the browser.
- Cloudflare D1 A small SQL database on the free tier for attendance: members, sessions, answers with their notes, away dates, duties, rides, game sheets, polls and votes, check-ins, profile change requests, charges and payments for dues tracking, guests, offers to sub (one event or standing), asks for subs, line-ups, who came, shared snapshots with their pictures, devices for reminders and which reminders went out, members' login emails, passkeys' public keys and linked Google or GitHub accounts, and settings (the club's own reminder keys, when the reminder run last started, a fingerprint of the tables). No passwords; answers are kept for the current season and the one before, then only as totals.
-
4In the browser
- Leaflet + OpenStreetMap Self-hosted map library; tiles load as a map comes into view.
- Schedule calendar Agenda, day, week, month and year views with team filters, written in plain JavaScript.
- Photo viewer Native dialog with zoom, pan, swipe, pinch, keyboard and full screen. No library.
- Installable app Web app manifest and a service worker: the site installs on phones, saved rink cards open without a signal, and attendance reminders arrive by Web Push. The worker shows each reminder, opens the event on a tap and tells open windows that it came.
- Website attendance In or Out on event pages, cards and Your events, notes, subs, the reminder banner, reminders on the device, the coach tools, the history and People (attendance.js, loaded only while attendance is switched on, with each page's own part in a module that only that page loads).
- Line-up builder Coaches drag or tap players onto four lines, three defense pairs and two goalies, publish it for the team and print a bench sheet (lineup.js).
- Share pictures Pictures for the team chat (who still has to answer, a line-up), drawn on the coach's device in the club's colors, font and emblem (share-card.js).
- Search pop-up Results as you type, grouped by kind, with filters and recent searches; one module runs the Pagefind searches for the pop-up and the search page (search-kit.js).
- instant.page Preloads same-site pages on hover or touch.
- Self-hosted fonts Barlow Condensed for headings ships with the site (SIL Open Font License); body text uses the system font. No third-party font server.
- Light and dark The colors follow the device's light or dark setting through CSS alone. There is no toggle and nothing is stored.
- Lucide Inline SVG sprite with the icons of icon-only controls, such as close, previous and next, the info button, the menu and the tabs of the members' bar on phones. No icon sits beside text.
-
5Services
- Web3Forms + Turnstile Form delivery to the club inbox with a cookie-free spam check and a honeypot.
- GTranslate Machine translation, loaded only when a visitor presses Sprache / Language in the menu.
- Resend Sends the six-digit codes that members log in with for website attendance, on the free plan; the site stops at 90 a day and 2,900 a month. Nothing else is sent by email.
- Google and GitHub sign-in Continue with Google or GitHub for website attendance (OAuth with PKCE): the provider tells the site only that the member signed in and their verified email.
Quality gates
We check every change to the code with these gates before it goes live. The build itself checks content saved in the CMS, and if it fails, the previous version stays online. We made each gate fail on purpose before we trusted it.
- 1CMS configThe editor configuration parses and every field, choice, rule and reference matches the content.
- 2StyleColors, sizes and spacing come from the design tokens; two breakpoints only.
- 3AttendanceThe attendance API against an in-memory database: signing in (codes and their limits, passkeys with a simulated authenticator, Google and GitHub against stand-ins), who may answer, notes, coaches' answers, the lock at the start, privacy of names, line-ups, shared links, offers to sub and asks for subs, Web Push reminders (the encryption checked against the RFC 8291 example), who came, the history, the season clean-up, admin rights and the off switch.
- 4BuildAll pages, feeds, images and the search index.
- 5CalendarsEvery feed parsed by an independent iCalendar library and checked line by line.
- 6SEOUnique titles and descriptions, canonical, Open Graph, structured data, alt text and the security policy for inline scripts on every page.
- 7Accessibilityaxe-core on every page in both themes at phone width, plus overflow and tap-target checks.
- 8App and offlineManifest, service worker and offline pages work with the network cut.
- 9LighthousePerformance, accessibility, best practices and SEO at 95 or better on key pages, before a release.
Security and privacy headers
Every response carries a strict Content Security Policy, HSTS, frame denial, a referrer policy and a permissions policy. Our privacy policy sets out what each service handles, and the accessibility statement the standards we aim for. The style guide shows the components and tokens, and the webmaster's guide explains how to run, deploy and hand over the website.