DEV Community

member_5432fd74
member_5432fd74 Subscriber

Posted on

Content That Expires: Building Event Pages That Stay Honest After the Event

Most content on a site is roughly as true next year as it is today. Event content is not. A page about a festival, a conference, a deadline, or a seasonal window is correct for a narrow period and quietly wrong for the rest of its life, and nothing in the stack tells you when it crossed over.

The failure is familiar. Somebody searches for an annual event, lands on last year's page still ranking, and reads dates that have not been true for eleven months. The page is not broken. No monitor fires. It just lies.

Expiry is a property of the content, so it belongs in the content model.

Model the window, not the publish date

The instinct is to add updatedAt and show it. That records when someone touched the file, which is not the same as whether the facts still hold. A page edited last week can still carry last year's dates.

What you want is the window the content describes:

type EventWindow = {
  startsAt: string      // ISO date the event begins
  endsAt: string        // ISO date it ends
  verifiedAt: string    // when a human last confirmed these dates
  recurrence?: 'annual' // whether a successor page is expected
}
Enter fullscreen mode Exit fullscreen mode

Three states fall out of that, and each one wants different output:

State Condition What the page should do
Upcoming now < startsAt Lead with dates and planning info
Live startsAt <= now <= endsAt Lead with today, drop the countdown framing
Past now > endsAt Say so above the fold, link the successor

The third state is the one everyone skips, and it is the one that decides whether the page is honest.

Render the state, do not just store it

A past-state page should not silently keep its future-tense copy. The minimum is a dated line at the top saying the event has ended, plus a link to the next occurrence if one exists. That costs a conditional:

{state === 'past' && (
  <p>
    This covers the {year} event, which ended {formatDate(endsAt)}.
    {successorUrl && <a href={successorUrl}>See the {year + 1} guide</a>}
  </p>
)}
Enter fullscreen mode Exit fullscreen mode

Keeping the old page rather than redirecting is usually right. It holds its links and its history, and people do search for past editions. What is not right is letting it present itself as current.

Emit the dates as structured data too

If the dates live in the model, Event schema is nearly free, and it gives search engines an explicit signal about the window instead of making them infer it from prose.

Worth pairing with an honest dateModified. Bumping that on every build to look fresh is a habit worth dropping, because it decouples the signal from any real editorial act and there is no upside once the freshness is not believed.

Watch for the window, not for errors

Expired content produces no errors, so ordinary monitoring never sees it. The check is a scheduled job that queries for content whose endsAt has passed without a successor, and whose verifiedAt is older than some threshold. That query is the entire feature:

SELECT slug, ends_at, verified_at
FROM event_pages
WHERE ends_at < now()
  AND successor_slug IS NULL
ORDER BY ends_at DESC;
Enter fullscreen mode Exit fullscreen mode

Run it weekly and it becomes an editorial queue rather than an incident.

A live example

Annual events are the clearest case because the same page shape recurs forever. The Sturgis Motorcycle Rally runs a fixed August window each year, and its 2026 guide is a decent example of the upcoming state done properly: the dates are stated explicitly rather than as "this year", the verification date is visible, and the recurrence is obvious enough that a successor page is expected rather than an afterthought.

The general rule is that any page describing a moment should know when that moment ends. Store it, render it, and query for it.

Top comments (0)