How I Built Expiring Links With Zero Backend (React + TypeScript Only)

Published: (April 28, 2026 at 10:12 PM EDT)
3 min read
Source: Dev.to

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:

  1. Takes the destination URL.
  2. Takes the expiry timestamp (Unix ms).
  3. Combines them into a JSON object.
  4. Encodes the JSON with btoa() (Base64).
  5. Appends the encoded payload as a URL parameter.
  6. 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

LimitationDetails
URL lengthBase64‑encoding a JSON object adds characters. TinyURL shortens the link, but the intermediate URL can be long.
No server‑side validationA 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 analyticsWithout server‑side storage, you can’t track click counts or view a dashboard.
Cannot revoke earlyOnce 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.

0 views
Back to Blog

Related posts

Read more »