Introduction
Setting an expiration date alone doesn't make content disappear on its own. You need something to "watch whether the deadline has arrived and, when it does, unpublish it" — a watchdog.
I gave that role to Vercel Cron (a scheduled-execution mechanism).
In this article, I'll dig into two things: how cron detects and unpublishes expired content, and what you should return when someone hits an expired page.
Behind the experience of "content that expires automatically," some quiet but important processing is running. I'll share the big picture along with the implementation know-how.
Expiration Handling with Cron
Designing daily execution
The expiration-check cron runs daily (once a day).
Campaign and event deadlines are almost always day-level — "valid until this date" — and minute-level immediacy isn't required. So there's no need for high-frequency checks like every minute; once a day is plenty.
Raising the frequency only increases the number of runs, the monitoring overhead, and the cost. Choosing a cadence that isn't excessive for the requirement leads to sustainable operation. Only when strict, instant expiration is needed do you add KV's TTL alongside it.
Vercel Cron invokes the expiration process on a daily schedule
Fetch the content list from KV and compare each expiration (expiresAt) with the current time
Lower the publish flag on anything past its deadline, or let TTL delete it
Log what was expired so it can be traced later
The expiration detection logic
The detection itself is almost anticlimactically simple.
It just compares each content item's expiresAt in KV against the current time. If "expiresAt < now," it's expired, and it's added to the unpublish list. Because the expiration date lives in the data itself, the detection logic consolidates into one place.
To fetch the listing, I use the slug index prepared on the storage side. Even scanning every item is fine because the count is modest, so there's virtually no performance concern. A straightforward build translates directly into maintainability.
Being mindful of idempotency
One thing I always watch for when writing cron processing is idempotency.
This is the property that "running the same process any number of times yields the same result." Cron occasionally double-fires or gets re-run after a mid-way failure. When that happens, re-expiring something that's already expired must not cause anything weird.
"Unpublish if expired" is inherently idempotent — the result doesn't change no matter how many times you run it. Keeping this property lets you design retries and error behavior with peace of mind.
Access Control After Expiration
Return a 404, or show a notice?
When someone hits an expired page, there are broadly two options.
One is to return a 404 (page doesn't exist). The other is to show a notice page saying "this page is no longer available." Which is better depends on the nature of the content.
For a fully disposable internal URL, a 404 is fine. But for a URL like a campaign LP shared on social media, which people may click later in good faith, a notice is more considerate. Rather than assuming "expiration equals instant 404," it's best to design the experience too.
A double check at display time
Because cron runs daily, a small gap opens up "between passing the deadline and the next cron run."
To keep a stale page from showing in that gap, the public page also checks the expiration on every display. The moment it fetches data from KV, it compares expiresAt with the current time and, if it's past, treats it as expired on the spot.
Cron is the "batch cleanup crew," and the display-time check is the "on-the-spot judge." With this two-layer setup, there are no holes in the timing of expiration. This just-in-case defense raises overall reliability.
Combining with access control
Some temporary pages need their audience narrowed in the first place.
For example, documents you want only a business partner to see, or temporary internal sharing. In such cases, on top of expiration, you control access itself — by using a hard-to-guess slug (URL) or applying a simple viewing restriction.
"Expires on a deadline" and "only certain people can view it" are separate mechanisms, but combining them broadens usability. Designing to fail safe in both cases — expired and unpublished — brings peace of mind.
Mind the gap
Relying on the cron interval alone lets a stale page show during the window "after expiry but before the next cron." Always add a display-time double check to avoid missing an expiration.
Conclusion
Cron-based auto-expiration is a mechanism that quietly does the simple job of "watching the deadline and unpublishing" in place of a human. The key points were a daily cadence matched to the requirement, idempotent processing, and a display-time double check.
And don't forget how you present things after expiration. Whether you cut it off with a 404 or politely show a notice — designing this far means you never leave customers feeling shut out.
How the data this cron expires is stored is covered in "Managing Temporary Content with KV Storage." Where this kind of mechanism shines is introduced in "Use Cases for Disposable URLs." For the big picture, start from the hub article "Auto-Expiring Content."