Case study · 2026

Liga Astra: a tennis league that publishes its own results

Klub Astra's amateur tennis league moved from WordPress, Google Sheets and embedded dashboards to a fast static site with a small admin panel, where every saved score updates the public tables and the club ranking.

Client
Sports club, amateur league
Status
Live
Scope
Website design and build, data model, admin panel, newsletter, move off WordPress

Visit the live site: liga.tenisgrodzisk.pl (opens in a new tab) liga.tenisgrodzisk.pl

Liga Astra home page on a desktop screen: the heading Amatorska liga tenisowa Klubu Astra over a dusk tennis court, buttons for the tables and the club ranking, and counters for players, matches played and leagues.
Liga Astra home page on a phone: the club logo, a menu button, the league heading and the two buttons stacked.

Problem

The league’s site ran on WordPress, but almost nothing on it was WordPress. Each league page showed five windows into one Google Sheet, cut out by hard-coded cell ranges. Rankings came from two more sheets, the home page and the history pages were Looker Studio dashboards, and scores were sent through an embedded Google Form.

That arrangement had real costs:

  • It was wrong in places. A survey of the live site found rows for players who did not exist, a league table that contradicted its own results, and a ranking column showing a spreadsheet error to every visitor.
  • It was invisible. Content inside an embedded sheet cannot be indexed, and on a phone it scrolls separately from the page. A league table is exactly what players search for.
  • It had no memory. Each round overwrote the last, so history existed only in the dashboards.
  • It was fragile. Every roster change meant editing embed addresses in WordPress, and 25 of the 41 indexed pages could not be reached from the menu at all.

Approach

We started with a written survey of what the old site actually was, page by page, before proposing anything. A cheaper option, keeping the spreadsheets and reading them at build time, was costed and set aside, because it would keep every one of the failures above.

The club chose a database as the single source of truth. Three decisions shaped the rest:

  • Make wrong data impossible, not just unlikely. A match must name two real players, and the standings are calculated from the results rather than typed next to them, so the table can never contradict the results.
  • Keep the public site static. Visitors get plain, fast pages. The database is never exposed to the internet; the site is rebuilt from it when something changes.
  • Rebuild only what people use. The 16 pages in the menu were rebuilt, and the other old addresses are redirected.
The league site, before and afterTwo columns. Before: scores sent through a Google Form, one Google Sheet tab per league overwritten each round, five embedded windows per league page, Looker Studio dashboards as the only history, all shown inside WordPress pages. After: scores entered on a phone with a code, an admin panel for the organiser, one league database that calculates the standings, a static website rebuilt from it, and a weekly results email the organiser approves.BeforeScores sent through a Google FormOne sheet tab per league, overwritten eachroundFive embedded windows per league pageLooker Studio dashboards, the only historyWordPress page around it allNot indexable, hard to use on a phone, and ableto contradict itself.AfterScores typed on a phone with a short codeAdmin panel for the organiserLeague database: standings calculated fromresultsStatic website, rebuilt from the dataWeekly results email, approved by theorganiserThe tables cannot disagree with the results.one sourceof truth forplayers andresultsThe league site, before and afterTwo columns. Before: scores sent through a Google Form, one Google Sheet tab per league overwritten each round, five embedded windows per league page, Looker Studio dashboards as the only history, all shown inside WordPress pages. After: scores entered on a phone with a code, an admin panel for the organiser, one league database that calculates the standings, a static website rebuilt from it, and a weekly results email the organiser approves.BeforeScores sent through a Google FormOne sheet tab per league, overwritten each roundFive embedded windows per league pageLooker Studio dashboards, the only historyWordPress page around it allNot indexable, hard to use on a phone, and able tocontradict itself.one source of truth forplayers and resultsAfterScores typed on a phone with a short codeAdmin panel for the organiserLeague database: standings calculated fromresultsStatic website, rebuilt from the dataWeekly results email, approved by the organiserThe tables cannot disagree with the results.
Read the diagram as text
  1. Before: Scores sent through a Google Form, then One sheet tab per league, overwritten each round, then Five embedded windows per league page, then Looker Studio dashboards, the only history, then WordPress page around it all. Not indexable, hard to use on a phone, and able to contradict itself.
  2. one source of truth for players and results
  3. After: Scores typed on a phone with a short code, then Admin panel for the organiser, then League database: standings calculated from results, then Static website, rebuilt from the data, then Weekly results email, approved by the organiser. The tables cannot disagree with the results.

Implementation

  • Public site. League tables, results grids, fixtures, player pages, a club ranking for men and women, statistics and the regulations, 32 pages in all. Navigation and wide tables were designed for phones first: a drawer instead of hover menus, and a results grid that scrolls sideways with the names fixed.
  • Admin panel. In Polish, for one organiser: create a round by pasting the rosters (fixtures are generated), edit and merge players, move a player between leagues with a preview of what changes, import old seasons from CSV, and print empty A4 fixture sheets.
  • Score entry from a phone. Whoever types in the scores opens one public address and enters a code made of three Polish words. The phone is remembered, and the code opens the results grid and nothing else.
  • Publishing. Saving a score triggers a rebuild, and the public pages follow in about two minutes. A diagnostics page in the panel explains, step by step, why a saved score has not appeared yet.
  • Weekly newsletter. Every Monday a results summary is drafted automatically and emailed to the organiser, who approves or rejects it. Nothing is sent without that approval. The subscriber list was recovered from the old WordPress plugin, with each person’s consent status kept.
  • Regulations as real pages. The rules were moved out of an embedded PDF into readable pages with a link to every clause, so they can be found by search and quoted in a message.
  • A switch-over that could be undone. The new site was staged at a private address first. Going live was a pair of renames with an automatic rollback, and the old WordPress install was moved aside rather than deleted.
  • Checks on every change. Automated tests cover the data rules, the score parser and the contract between the PHP export and the site, and every internal link is checked on each build.
From a saved score to the public tablesFive steps: a score typed on a phone, saved and checked, standings recalculated from the results, the site rebuilt from the data, and the public tables and ranking updated, in about two minutes.PersonScore typed ona phonethree-word codeSaved andcheckedin onetransactionStandingsrecalculatedfrom theresults, nevertypedSite rebuiltfrom the dataautomaticPublic tablesupdatedabout 2 minutesFrom a saved score to the public tablesFive steps: a score typed on a phone, saved and checked, standings recalculated from the results, the site rebuilt from the data, and the public tables and ranking updated, in about two minutes.PersonScore typed on a phonethree-word codeSaved and checkedin one transactionStandings recalculatedfrom the results, never typedSite rebuilt from the dataautomaticPublic tables updatedabout 2 minutes
No one presses a publish button. If a score has not appeared, the panel's Publikacja page says which step is holding it.
Read the diagram as text
  1. Person: Score typed on a phone (three-word code)
  2. Saved and checked (in one transaction)
  3. Standings recalculated (from the results, never typed)
  4. Site rebuilt from the data (automatic)
  5. Public tables updated (about 2 minutes)

Outcome

  • The new site has been live since 17 September 2026, replacing WordPress, the embedded sheets, the Google Form and the Looker Studio dashboards.
  • The tables and the club ranking are calculated from the results, so the inconsistencies found on the old site cannot reappear.
  • The organiser runs the league from one panel, and scores entered from a phone reach the public pages without anyone pressing a button.
  • League pages can be read on a phone and indexed by search engines.
  • The front page hero image is served as responsive WebP: a phone downloads a 19 kB version instead of the 2.6 MB original upload.
From survey to a league that publishes itself, 2026A timeline of 9 events, from 30 Aug to 13 Oct.30 AugSurvey of the old site;database chosen31 AugAdmin panel and dataexport started4 SepFirst staging deploy;newsletter; CSV import9 SepReal league dataimported10 SepScore entry link for thescorer11 SepThree-word codes;switch-over tooling17 SepLive21 SepRegulations as pages13 OctMonday draft sent forapprovalFrom survey to a league that publishes itself, 2026A timeline of 9 events, from 30 Aug to 13 Oct.30 AugSurvey of the old site; database chosen31 AugAdmin panel and data export started4 SepFirst staging deploy; newsletter; CSVimport9 SepReal league data imported10 SepScore entry link for the scorer11 SepThree-word codes; switch-over tooling17 SepLive21 SepRegulations as pages13 OctMonday draft sent for approval
Dates are from the project's commit and pull request history.
Read the diagram as text
  1. 30 Aug: Survey of the old site; database chosen
  2. 31 Aug: Admin panel and data export started
  3. 4 Sep: First staging deploy; newsletter; CSV import
  4. 9 Sep: Real league data imported
  5. 10 Sep: Score entry link for the scorer
  6. 11 Sep: Three-word codes; switch-over tooling
  7. 17 Sep: Live
  8. 21 Sep: Regulations as pages
  9. 13 Oct: Monday draft sent for approval

What this shows

A small club’s website problem is often a data problem wearing a design problem’s clothes. Once there was one trustworthy source for players and results, the fast pages, the phone-friendly tables and the weekly email were straightforward to build on top of it.

Stack
Astro, Tailwind CSS, TypeScript, Zod, PHP, MariaDB, GitHub Actions
Type
Strategy + implementation
  • website
  • sports
  • league
  • admin-panel
  • newsletter
  • wordpress-migration
  • static-site
  • mobile-first

Book a 30-min scoping call

Bring one process. We will tell you whether automation changes it, and what it would take.