DEV Community

Cover image for I built a free, self-hosted URL shortener on Cloudflare Workers + D1
cligc
cligc

Posted on

I built a free, self-hosted URL shortener on Cloudflare Workers + D1

Most link shorteners either cost money once you need analytics, or send your visitors' data to someone else's servers. I wanted one that:

  • runs on my own Cloudflare account, for free,
  • redirects at the edge (fast everywhere),
  • shows real analytics (not just a click counter),
  • and can tell me how my sites are doing, not only the links.

So I built cligc link — an open-source (MIT) short-link service that runs entirely on Cloudflare Workers + D1. No server, no container, no database to manage.

Site list

What it does

Short links

  • Route by country / region and device: Hong Kong visitors go to one page, iPhone users to the App Store, everyone else to the default URL
  • A/B tests: split any destination by weight; the same visitor always gets the same variant
  • QR codes for every link (SVG or 1024 px PNG)
  • Open apps directly: a link can point to tg://, weixin:// and other app schemes, with a web fallback if the app isn't installed
  • Health checks: if a destination starts returning 404 or times out, you get a Telegram / email alert
  • Edit history with one-click undo, expiry dates, bulk replace

Analytics

  • Real-time visits, sources, countries, devices, hourly distribution
  • Human vs. bot detection (crawlers, link previews, data-center traffic)
  • Source tracking with ?src= and optional UTM tagging, so GA4 can show conversions per link

Sites & SEO

  • Connect Google Analytics 4, Search Console and Bing Webmaster: every site's traffic and search performance on one page
  • Checks whether each URL is indexed by Google and Bing, and can submit missing URLs to Bing
  • Suggestions generated from your own data: GA4 suddenly reporting nothing, clicks dropping, high impressions with low CTR, low indexing rate, broken URLs…

Site page with suggestions

How it works

visitor ──► Cloudflare edge ──► Worker ──► 302 to the destination
                                   │
                                   └──► D1: visit record (async)
Enter fullscreen mode Exit fullscreen mode
  • One Worker handles everything: redirects, the admin SPA (a single HTML file), the JSON API and an hourly Cron Trigger.
  • D1 (Cloudflare's SQLite) stores links, rules and visit records. The schema is created automatically on the first request, so there's no migration step when you deploy.
  • The redirect happens first; recording the visit goes through ctx.waitUntil, so logging never slows down the redirect.
  • The hourly cron aggregates visits into hourly / daily tables, so charts read a few aggregate rows instead of scanning every visit. Anything after the last aggregation is read from the raw table, so stats are always real-time.
  • The product home page is built as static assets, so serving it doesn't count against Worker requests.

Deploy it in about 5 minutes

Option 1: click the Deploy to Cloudflare button in the README. It creates the Worker and the D1 database for you.

Option 2: command line (Node.js 18+):

git clone https://github.com/ccvar/cligc_link.git
cd cligc_link
npm install

npx wrangler login
npx wrangler d1 create cligc-link   # put the printed database_id in wrangler.toml
npx wrangler deploy
Enter fullscreen mode Exit fullscreen mode

Then open https://<your-worker>/admin and sign in. The first sign-in forces you to set a new password (or set one before deploying with npx wrangler secret put ADMIN_PASSWORD).

To use your own domain (it must be on Cloudflare), add a route to wrangler.toml and deploy again:

routes = [
  { pattern = "go.example.com", custom_domain = true },
]
Enter fullscreen mode Exit fullscreen mode

Is it really free?

For most individuals and small teams, yes. The free plan gives you 100,000 Worker requests a day, and D1 gives you 5 million rows read and 100,000 rows written a day.

The honest catch: D1 writes run out first. Each visit writes one record, but D1 also counts a row for every index and the autoincrement counter. Add the link's counter and the hourly aggregates, and a visit costs about 10 written rows. That means the free plan can fully record roughly several thousand to 10,000 visits a day. Beyond that, the Workers Paid plan ($5/month) includes 50 million written rows a month.

The admin shows your Cloudflare usage and forecasts when you'll run out, and can alert you before you do.

Things I learned building on Workers + D1

A few things that weren't obvious from the docs:

  1. D1 limits a statement to 100 bound parameters. WHERE id IN (?, ?, …) breaks as soon as a list gets long. Passing a JSON array as a single parameter and using IN (SELECT value FROM json_each(?)) fixes it.
  2. Write amplification is real. Every index on a table is another written row per insert, so each index is a cost to weigh against the reads it speeds up. Most reads go to small aggregate tables instead of the raw visits table.
  3. When the D1 daily quota is exhausted, every query fails until 00:00 UTC — including the ones your redirects depend on. Design for it: show a clear "temporarily unavailable" page and warn the owner before it happens.
  4. A Worker invocation can only have 6 outbound connections open at once. For slow APIs (Google's URL Inspection API takes several seconds per call), the time budget matters more than the request limit. Running independent API calls concurrently (Google and Bing checks side by side) roughly doubled throughput.
  5. Hourly cron runs have a wall-clock limit, so long jobs need a deadline measured from when the run started — including any time spent waiting for a lock — not from when the work began.
  6. *.workers.dev is usually blocked in mainland China. If you have visitors there, put the Worker on your own domain before sharing links.

Try it

The admin is available in English and Chinese. Issues and PRs are welcome, and if it's useful to you, a ⭐ on GitHub helps more people find it.

Also by me: cligc.com — 170+ free tools that run entirely in your browser (JSON formatter, image compressor, PDF merge, QR code generator…). No ads, no sign-up, no uploads.

Top comments (0)