Most "expiring link" tools work the same way: generate a link, store the destination and expiry in a database, check the database on every click, redirect or block accordingly.
That's the obvious approach. It's also the one that requires a backend, a database, server costs, and a breach surface.
I had a constraint: React + TypeScript only, deployed on Vercel, no Node.js, no database, no backend whatsoever.
So I had to find a different way.
The Core Idea: Put Everything in the URL
Instead of storing link data on a server, encode it directly into the URL itself.
When a user creates an expiring link, the app:
- Takes the destination URL
- Takes the expiry timestamp (Unix ms)
- Combines them into a JSON object
- Encodes it with
btoa()(base64) - Appends it as a URL parameter
- Shortens the full URL via TinyURL's API
const payload = {
url: destinationUrl,
exp: expiryTimestamp
};
const encoded = btoa(JSON.stringify(payload));
const longUrl = `https://onetimelink.vercel.app/r?d=${encoded}`;
// Then shorten
const shortUrl = await shortenWithTinyURL(longUrl);
When someone visits the short link, TinyURL expands it back to the long URL. The React app decodes the parameter client-side:
const params = new URLSearchParams(window.location.search);
const encoded = params.get('d');
if (!encoded) {
// Invalid link
return;
}
try {
const payload = JSON.parse(atob(encoded));
const now = Date.now();
if (now > payload.exp) {
// Expired — show expiration screen
setExpired(true);
} else {
// Valid — redirect
window.location.href = payload.url;
}
} catch {
// Malformed link
setInvalid(true);
}
No database query. No server call. No stored data anywhere.
Why This Works
The link carries its own expiry inside itself. The server never needs to know anything. The decision to redirect or block happens entirely in the browser, client-side, on every click.
This has some interesting properties:
Zero storage = zero breach surface. There's no database of sensitive links sitting on a server somewhere. The data exists only in the URL you shared. If the URL is lost, the data is gone.
No server costs. The entire product runs on Vercel's free tier. No compute happens server-side on link visits.
Works offline (partially). If someone has the URL cached, the expiry check still works because it's just a timestamp comparison — no network request needed.
The Tradeoffs
This approach has real limitations worth being honest about.
URL length. Base64-encoding a JSON object adds characters. TinyURL handles the shortening, but the intermediate URL before shortening is long. If TinyURL's API rate limit is hit, the long URL is returned as a fallback — functional but not pretty.
No server-side validation. A determined user could decode the base64, modify the expiry timestamp, re-encode it, and create a "non-expiring" version of the link. For most use cases this doesn't matter — the threat model is casual oversharing, not adversarial attacks. But it's worth knowing.
No analytics. Since nothing is stored server-side, there's no way to know how many times a link was clicked. You get one-directional expiry enforcement, not a dashboard.
Can't revoke early. Once a link is created, it expires when it expires. There's no mechanism to kill it early because there's no server-side record to delete.
What It's Actually Good For
This architecture is a good fit for the specific problem it solves: a freelancer or agency owner sharing something sensitive with a client, once, with a defined window.
- Temporary login credentials for a handoff
- A staging environment link during a review period
- A private document with a natural end date
For these use cases, the limitations above don't matter. The client clicks once, gets what they need, and after the window closes the link is dead.
For anything requiring analytics, multi-click tracking, or early revocation — you need a proper backend. This approach deliberately trades those features for zero storage and zero complexity.
The Stack
- React + TypeScript
- Vite
- Vercel (free tier)
- TinyURL API (free tier, no auth required)
-
btoa()/atob()for encoding — built into every browser, no library needed
Total server infrastructure cost: $0.
Try It
If you're building something where users need to share sensitive links temporarily, the approach above is a legitimate alternative to a full backend for certain use cases.
The live implementation is at onetimelink.vercel.app — free to use, no account needed.
More on the architecture and the product direction on the OneTimeLink blog.
Still building in public. 408 organic users, $0 revenue, first paying customer is the only milestone that matters right now. Follow along if you want the unfiltered version.