What I built.
VisionLink URL Shortener is part of my portfolio of websites and applications.
- Server-side short-slug resolution.
- Reserved-route and asset-path handling.
- Forwarding of destination query parameters.
Under the hood.
Nuxt / Nitro
Request handling before the Vue application
Supabase
Resolve a slug against digital_cards
TypeScript
URL and routing helpers
- Incoming slug
- Path guards
- Card lookup
- Query merge
- HTTP 302
One link at the start of the journey
Sharing a digital card should be straightforward. A short, readable address gives someone a clean way into the card without making the full application URL part of every conversation.
The request never reaches a component
The interesting file here is server/middleware/00-short-redirect.ts. It runs in Nitro, before the Vue application. The handler removes trailing slashes, skips framework and API prefixes, and accepts only one path segment. A dotted path is ignored too, which keeps a request for an asset from becoming a card lookup.
Only after those checks does it normalise the slug and query digital_cards for card_url. An empty destination redirects to the root. A resolved destination gets an HTTP 302. There is no loading spinner or client-side navigation step in this path.
The subtle part is the query string
The helper uses URLSearchParams to carry incoming parameters forward. If the target already contains a key, its value wins. That is a small policy decision with visible consequences: a shared link can carry extra context without overwriting parameters already configured on the card.
The response also sets Cache-Control to private, no-store. That favours a fresh destination over caching this redirect. It is a trade-off I want to explain explicitly; I have not published a benchmark that would justify calling the service fast by a particular number.
A small service with a narrow responsibility
For a redirect, the best interface is often no interface at all. I want the server to do the small amount of work required and send the visitor straight to the card. The management tools are a separate concern.
What I take from it
This is a compact example of putting a responsibility at the right layer. A small piece of server logic can remove unnecessary work from the public journey.
if (isAppOrAssetPath(path)) return;
const slug = normalizeShortSlug(path);
const destination = await lookupCardUrl(slug);
setHeader(event, "Cache-Control", "private, no-store");
return sendRedirect(event, mergeQuery(destination) || "/", 302);Explanatory pseudocode, shortened for readability.