How I Built Expiring Links With Zero Backend (React + TypeScript Only)
Source: Dev.to
Introduction
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, and redirect or block accordingly.
That approach requires a backend, a database, server costs, and a breach surface.
I had a constraint: React + TypeScript only, deployed on Vercel, with no Node.js, no database, no backend whatsoever.
The solution is to encode the link data directly into the URL itself.
How It Works
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 the JSON with
btoa()(Base64). - Appends the encoded payload as a URL parameter.
- Shortens the full URL via TinyURL’s API.
// Create payload
const payload = {
url: destinationUrl,
exp: expiryTimestamp
};
const encoded = btoa(JSON.stringify(payload));
const longUrl = `https://onetimelink.vercel.app/r?d=${encoded}`;
// Shorten (TinyURL)
const shortUrl = await shortenWithTinyURL(longUrl);
When someone visits the short link, TinyURL expands it back to the long URL. The React app then 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. The link carries its own expiry, and the decision to redirect or block happens entirely in the browser.
Benefits
- Zero storage → zero breach surface. The data exists only in the shared URL.
- No server costs. The product runs on Vercel’s free tier; link visits involve no server‑side compute.
- Works offline (partially). If the URL is cached, the expiry check still works because it’s just a timestamp comparison.
Limitations
| Limitation | Details |
|---|---|
| URL length | Base64‑encoding a JSON object adds characters. TinyURL shortens the link, but the intermediate URL can be long. |
| No server‑side validation | A user could decode the payload, modify the expiry timestamp, re‑encode it, and create a “non‑expiring” link. Suitable for casual use cases, not adversarial scenarios. |
| No analytics | Without server‑side storage, you can’t track click counts or view a dashboard. |
| Cannot revoke early | Once created, a link expires only when its timestamp passes; there’s no way to delete it early. |
Ideal Use Cases
- Temporary login credentials for a handoff.
- Staging environment link during a review period.
- Private document with a natural end date.
For these scenarios, the limitations are acceptable: the client clicks once, gets what they need, and after the window closes the link is dead.
If you need analytics, multi‑click tracking, or early revocation, a proper backend is required.
Tech Stack
- React + TypeScript
- Vite for bundling
- Vercel (free tier) for deployment
- TinyURL API (free tier, no authentication)
- Built‑in browser functions
btoa()/atob()for encoding
Total server infrastructure cost: $0.
Conclusion
When you need to share sensitive links temporarily without the overhead of a backend, encoding the expiry data in the URL provides a viable, zero‑cost alternative. The live implementation is available at – free to use, no account required.
More details on the architecture and product direction can be found on the OneTimeLink blog.