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

- 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.
- 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.
- 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.
- 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.
- 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.
- 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
Editors
- Strapithree languages, 85 fields
- S3posters and photos
Data
- PostgreSQL
- One Event tablesplit by date
Site
- Next.js on Vercel
- Regenerationevery 5 minutes
Visitor
- Programme and archive
- Ticket vendordeep link, per language


The part that never makes it into a portfolio. All of it happened, and all of it is in the commit history.
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_BUCKETwhile another readAWS_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”.
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.
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]=datedefaults 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.
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
useStatekeeps 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.
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.
- Scaffold, with a Firebase plan that lasted a month
- The build starts: 114 commits in 20 days
- “Fix ui”: the Tag Manager patch
- The archive learns to sort
- Last front-end commit: the poster ratio
- 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.
Or ask the digital mind about it