Works05 / 05

A promoter’s twenty-year record moved off a Drupal theme that still shipped IE6 stylesheets: three languages in twenty days of commits, and still being fed in October 2026 on front-end code last changed in February 2024.

artforte.lv

The ArtForte home page with a red curtain and the AF monogram
artforte.lv — the first screen.
Client
ART Forte, a Latvian production company
Role
Engineering: site, content model, CMS
Period
Built January 2023; front end last changed February 2024
Stack
Next.js, TypeScript, Strapi 4, PostgreSQL, S3, Vercel, Heroku
Live
artforte.lv

ART Forte brings international opera, ballet, theatre and concerts to Latvia, by its own account since 2005. Its archive includes Anna Netrebko’s first concert in the Baltics and José Carreras in 2008.

In December 2022 that record lived on a Drupal site still shipping style.ie6.css, in two languages. A company dealing with international producers needs the opposite of that.

Three jobs: tell visitors what is on and send them to the right ticket page in their language; show twenty years of productions as a browsable record; and put every word on the site, down to the button labels, in the client’s hands.

  1. 01

    A public site in three languages

    Latvian, English and Russian: eleven routes in each. Lists are static with a five-minute regeneration window, detail pages render on request.

  2. 02

    A content model, not a custom admin

    Six Strapi content types, 85 fields, 67 of them localised. The admin itself is stock, on purpose: the work is the model. Every label is a CMS field, so a button can be reworded in three languages without a deploy.

  3. 03

    An event that retires itself

    Upcoming and archive are one table, split by date. The day after a show, it moves to the archive, and the place, date, price and Buy Ticket button disappear from its page. Nobody has to do anything.

  4. 04

    Ticket links, per language

    Each language’s entry links to the right page at the ticket vendor. No checkout, no card data, no stock to sync: the vendor keeps doing what it does. The cost is no view of the funnel past the click.

  5. 05

    Twenty years, browsable

    88 entries in Latvian, 86 in Russian, 82 in English, from 2006 to 2026. Translations are optional per entry, so a show can exist in one language and not another without a hole in the grid.

How it fits together

  1. Editors

    • Strapithree languages, 85 fields
    • S3posters and photos
  2. Data

    • PostgreSQL
    • One Event tablesplit by date
  3. Site

    • Next.js on Vercel
    • Regenerationevery 5 minutes
  4. Visitor

    • Programme and archive
    • Ticket vendordeep link, per language
The previous ArtForte site, a Drupal theme, captured in December 2022
Before: the Drupal site in December 2022, from the Wayback Machine.
The ArtForte archive grid of past productions
The archive: twenty years in one date-ordered grid.

The part that never makes it into a portfolio. All of it happened, and all of it is in the commit history.

  1. Fault 01

    One image, four gates, 30 commits in five days

    Symptom
    Images uploaded to the CMS would not reliably show. 22% of all commits in the project went here, in five days, with titles running “Fix Image url”, “1”, “2”, “3”, “final”, a revert the next morning, “4”, “Hardcode domain url”.
    Cause
    No single bug. A photo crosses four independently configured gates, and each failed in its own way: the admin’s own security policy, the object’s permissions on S3, the URL shape (the front prefixed a host onto URLs that were already absolute), and Next’s image allow-list. For nine hours one config read AWS_BUCKET while another read AWS_BUCKET_NAME. This reading comes from the diffs; the commits say nothing.
    Fix
    Each gate fixed in turn, then ImageWithFallback, so a failed image shows a designed placeholder rather than a broken icon. Headless stacks move failure into the seams: every piece worked alone, and no one config file owned “a photo appears”.
  2. Fault 02

    The analytics patch that unplugged React

    Symptom
    A commit titled “Fix ui”, April 2023. Its entire diff adds one element back to _document.tsx.
    Cause
    The evening before, adding Google Tag Manager had removed <NextScript/> along with the snippet it replaced. That element carries the hydration payload, so React could never take over the server’s HTML: the burger, the language picker, Load More and the newsletter form would all be dead. The symptom said UI; the cause was marketing tooling.
    Fix
    One line, restored after 19 hours 44 minutes. The failure would have been ugly rather than dark: pages are pre-rendered, so content and links survive with JavaScript gone. No test could have caught it, because there were none.
  3. Fault 03

    The archive that showed 2008 for a year

    Symptom
    The home page’s archive block led with the oldest shows instead of the latest. The Wayback Machine has it: four shows from 2008 in February 2023, the same four in July, one day after a major concert.
    Cause
    sort[0]=date defaults to ascending. It was written eleven days before the historic entries went in, when order was not visible. And it survived because it looks plausible: a wall of great names from 2008 reads as curation, not a bug.
    Fix
    date:desc, 372 days later. Bugs in data-heavy sites need the data to become visible, and someone who knows the data to look.
  4. Fault 04

    A translation is a row, not a column

    Symptom
    The language picker could not land on the right translation of a detail page. On news and archive, switching language left the previous language’s cards on screen.
    Cause
    Two traps. Strapi gives each translation its own numeric id, so the same show is 398, 399 and 400. And Next does not remount a page on a locale change, so useState keeps the old language’s list after the props have changed.
    Fix
    The picker reads the entry’s localisations and rewrites the id to the sibling’s. Lists compare the locale during render and reset themselves. The same non-remount trap turned up again on this very site, in the hero and in the chat.
  5. Fault 05

    The last commit was the client’s Instagram workflow

    Symptom
    Archive cards were built landscape. Uploaded images had quietly gone from 3861×2574 to 1080×1920, with file names that said so.
    Cause
    The team had started designing one vertical poster for Instagram Stories and reusing it everywhere. Nobody decided this; the assets simply changed under the design.
    Fix
    A new card ratio in February 2024, the last code change the front end has had. The final thing the code needed was not a feature. It was to fit how the staff actually work.

  • commits in the 20-day build7–26 January 2023, on 17 distinct days
  • archive entries in LV, RU, ENcounted live, 30 Oct 2026; 2006 to 2026
  • languages, up from 2the old site had Latvian and Russian
  • CMS fields localisedsix content types
  • days the archive showed 2008Wayback captures against the fix commit
  • months between the last front-end commit and the newest entryFebruary 2024 to 1 October 2026

Commit pulse

Every commit in the project’s repositories, on one line. The taller the bar, the hotter the stretch.

Dec 2022Apr 2025
  1. Scaffold, with a Firebase plan that lasted a month
  2. The build starts: 114 commits in 20 days
  3. “Fix ui”: the Tag Manager patch
  4. The archive learns to sort
  5. Last front-end commit: the poster ratio
  6. CMS moves hosts without a single front-end change

A content site earns its keep by how long it runs without you. Put the words in the client’s hands, let the data model do the retiring, and it keeps working years after the last commit. If your site needs a developer for every change, that is a fixable problem.

Write a brief

I reply fast on WhatsApp and Telegram

Or ask the digital mind about it
Next caseWinningBy AI