Skip to content
DevAlva
01Web DevelopmentMarch 2026

Houston Premier Soccer

Houston Premier Soccer

One event model for tournaments, weekly leagues, open play, and field rentals

A 7v7 facility in south Houston that needed to publish tournaments, recurring league nights, one-off open play, and rental availability from the same place. The hard part was not the design. It was that every one of those things is a different shape of time.

The job

Houston Premier Soccer runs 7v7 on a lit field in south Houston, off I-45 near 610. They sell four different things: single tournaments, seasonal leagues, drop-in open play nights, and private field rentals.

From the street those look like four items on a menu. In a database they are four genuinely different objects that happen to share a page. A tournament is a named thing with a registration window that opens and closes. A league is a recurring commitment. Open play happens once, on a Sunday, from seven to nine. A rental is a hole in the schedule that someone can buy.

The site has to publish all of it, keep it current, and let the people who run the facility change it without calling me.

What I found

The information was going out through social posts and word of mouth. That works until someone shows up on a Friday for a tournament that filled up on Wednesday, or calls to book a field during a league night.

The specific gap was that nothing anywhere told you the state of a thing. A flyer can tell you a tournament exists. It cannot tell you whether registration is still open, and that is the only question a player actually has.

What broke

The status badges lied.

The whole design leans on small state indicators. Registration Open. Upcoming. Fields: Open. They are the most useful thing on the page, because they answer the question before you have to ask it. I derived each one from timestamps rather than letting someone toggle it by hand, which was the right call, because a hand-toggled status is a status that is wrong by Monday.

What I missed is that a statically generated page freezes whatever was true at build time. The build ran, the registration window was open, the badge rendered Registration Open, and that HTML went into the CDN cache and stayed there. The window closed. The badge did not. Nothing errored, nothing logged, and the page looked perfect. It was just confidently out of date, which is the worst way for a page about deadlines to be wrong.

Recurrence made it worse. “Games are every Friday, starting August 21st” is a rule, not a list of dates. My first version expanded that rule into dates at build time, which meant the list of upcoming Fridays was also frozen, and the site would happily advertise a game night that had already happened. Anything storing local times across a DST boundary picks up an hour of drift on top of that, so an evening kickoff quietly becomes a different evening kickoff in November.

The fix

Store rules, render instances. A recurring event is stored as a start date and a recurrence, not as a pile of generated rows. Instances get expanded when the page is rendered, so the list of upcoming dates is computed relative to now rather than relative to whenever the last deploy happened.

Store instants, not wall clock. Times are held as absolute timestamps and formatted into the facility’s timezone at display. A 6:55 kickoff stays 6:55 for a person standing at the field regardless of what the calendar did in the fall.

Never cache a page past the moment it becomes wrong. The status is still derived, but the pages carrying time-sensitive state revalidate on a schedule short enough that a stale badge cannot outlive the thing it describes. Registration windows are the source of truth, and the badge is a read of that window, never a field someone maintains.

The four event types ended up as one model with a discriminator, which is why the tournament card, the league entry, and the open play listing all look like siblings on the page. They share a shape because they genuinely are the same shape, just with different rules attached. That was worth getting right early, because the alternative is four templates that drift apart the first time someone asks for a change.

What I’d do differently

I would have written the timezone handling down as a rule before writing any date code, instead of fixing it in three separate places after it bit me. “Absolute instants in storage, facility timezone at display, never a bare local date” is one sentence, and having it decided up front would have prevented all of it.

I would also have put a visible last-updated timestamp on the schedule from the beginning. Not because visitors care much, but because it is the fastest way to tell whether the page you are looking at is fresh, and it would have made the stale-cache problem obvious in a glance instead of a bug report.