DEV Community

Cover image for How I Built Expiring Links With Zero Backend (React + TypeScript Only)
Umair Shakoor
Umair Shakoor

Posted on

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

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:

  1. Takes the destination URL
  2. Takes the expiry timestamp (Unix ms)
  3. Combines them into a JSON object
  4. Encodes it with btoa() (base64)
  5. Appends it as a URL parameter
  6. 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);
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)