Open-source project
miantiao-me/Sink avatar
miantiao-me/Sink

Sink: a Cloudflare-only link shortener with analytics and no server to run

⚡ A Simple, Speedy, Secure, and Serverless Link Shortener with Analytics, Running Entirely on Cloudflare.

7,182 stars5,309 forksVueAGPL-3.0

At a glance

What is it?
Sink is an AGPL-3.0 link shortener built as a Nuxt 4 app on Cloudflare Workers, D1, KV and Analytics Engine. It suits individuals and small teams who already live in Cloudflare, and it is the wrong tool if you need multi-user accounts or a SLA.
Who is it for?
Adopt Sink if you already run Cloudflare and want a self-hosted shortener with click analytics, custom slugs, device and country routing, and no server to patch. Do not adopt it if you need per-user accounts, a managed SLA or an on-premise deployment, which the README points at S.EE for.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Vue, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Sink solves, and the audience it names

Running a shortener yourself usually means a small VM, a database, a reverse proxy and a certificate renewal you will forget about. Sink removes that layer by targeting one platform: Cloudflare. The README states the project focuses on individuals and small teams who want a simple, self-hosted shortener on Cloudflare, and it explicitly directs professional or business needs (managed service, multi-user, SLA) to S.EE, a separate product. That sentence is the most useful part of the README, because it draws the boundary for you. If you need several logins with different permissions, a support contract, or an uptime commitment, Sink is not the project to bend into that shape. If you want one token, one dashboard and a link that redirects in a few milliseconds from the edge, the scope matches. The feature list is ordinary for this category: shortening, custom slugs, UTM parameters, expirations, password-protected links, unsafe-link warning pages, QR codes, JSON import and CSV analytics export, plus i18n for the dashboard and redirect pages. Two items stand out as less common. Redirects can vary by device or by country, and the analytics view polls every ten seconds and replays events client-side rather than holding an SSE or WebSocket connection open. That last choice is a deliberate fit for Workers, where long-lived connections are awkward, and it is also the reason the dashboard feels like a stream rather than a live socket.

D1 as the source of truth, KV as a write-through cache

The storage split is the part worth understanding before you deploy, because it decides what breaks and what merely slows down. Cloudflare D1 is the authoritative link store and Workers KV is a write-through read cache. A write goes to D1 and then into KV, so a redirect on the hot path can be answered from KV instead of hitting the database. The consequence is that KV is not the record. If a KV entry is evicted, stale or missing, the link still exists in D1; the read falls back. The README does not describe the invalidation window in detail, and that is a real gap: if you delete or edit a slug, you are trusting the write-through path to have updated the cache, and the documentation does not state a rollback or a forced-purge command. Treat slug reuse as the risky case. Cloudflare R2 appears only for optional logical JSON snapshots, so it is a backup surface, not a serving path. Analytics is separate again, written to Cloudflare Workers Analytics Engine, which is why the dashboard can show a globe and event logs without you running a query engine. Three storage products means three sets of limits to learn, and the repository's own related searches include Cloudflare KV limits, which suggests that is where people get surprised. The .env.example also exposes NUXT_PUBLIC_KV_BATCH_LIMIT with a default of 50, a hint that the project is aware of how the cache is written in batches.

Installing Sink and shortening your first link

The repository requires Node 24 or newer and pins pnpm 11.11.0 through packageManager, so install that toolchain first. Clone the project, install dependencies, and copy the environment template. The postinstall script runs the map build and nuxt prepare, so expect the install step to do more than fetch packages.

bash
git clone https://github.com/miantiao-me/Sink.git
cd Sink
pnpm install
cp .env.example .env

Open .env and set NUXT_SITE_TOKEN to a strong private token. That value is the dashboard login and the API key, so it is the one secret you cannot leave as the placeholder. For local development you do not need the Cloudflare deployment variables yet; the script for local migrations applies the schema to a local D1 instance.

bash
pnpm db:migrate:local
pnpm dev

Nuxt dev serves on port 7465, which the dev script sets explicitly. Open that port, log in with the site token, and create a link. Leave the slug blank and the application generates one; NUXT_PUBLIC_SLUG_DEFAULT_LENGTH defaults to 6 if you want to change the length. When you are ready for a real deployment, the README recommends Cloudflare Workers and marks Cloudflare Pages as deprecated, so follow the Workers path. The deployment variables DEPLOY_D1_DATABASE_ID and DEPLOY_KV_NAMESPACE_ID rewrite placeholders in wrangler.jsonc into a gitignored wrangler.deploy.jsonc, and the deploy:worker script runs the remote migrations before wrangler deploy. If you skip those IDs, the deploy has nothing to bind to.

Where Sink is the wrong tool

The strongest limitation is stated by the project itself: no multi-user model, no SLA, no managed service. That is not a missing feature you can wait out; it is the product boundary. A second limitation follows from the architecture. Every dependency is a Cloudflare product, so Sink cannot run on a VPS, in a container on your own hardware, or on another cloud without rewriting the data layer. If your organisation requires data residency outside Cloudflare's network, or a deployment you can inspect on your own disks, this design rules itself out. The sibling project Slite is the escape hatch the README names: Sink and Slite are sibling versions of the same link-management and analytics project, with Slite running as a local Node.js 24+ or Docker process and keeping features, API contracts and file organisation compatible wherever practical. The README adds that Slite is not a fork replacement for Sink and neither is a legacy branch. So the choice is a platform choice, not a version choice. Third, the AI features depend on Cloudflare Workers AI and are optional; if you enable slug and OpenGraph generation, you are adding another Cloudflare service to the dependency list and another thing that can be unavailable. Finally, the README does not document rollback or cache purge behaviour, so an operator who needs a documented recovery procedure will be reading the source rather than the docs.

Sink against Kutt and Dub, and against its own sibling

The related searches around this project name Kutt and Dub, and the comparison is about where the code runs rather than what it does. Kutt is a self-hosted shortener you run as a Node.js service with your own database, which means you own the host, the backups and the upgrades, and you can put it anywhere. Sink inverts that: you own almost no infrastructure, but you accept Cloudflare as the substrate and the limits of D1, KV, R2 and Analytics Engine. Dub is the managed, commercial end of the same category, where the shortener is a service you pay for and the operational work is someone else's. Sink sits between them, closer to Kutt in spirit because you deploy it yourself, closer to Dub in feel because the dashboard and analytics are part of the product. The sibling comparison matters more for most readers. Slite is the same project shape as a local Node.js 24+ or Docker process with compatible features and API contracts. If your blocker is Cloudflare itself, Slite answers that question directly, and the README is explicit that neither version is a legacy branch. If your blocker is running anything at all, neither is the answer.

Licence, upgrade cost and what the repository tells you about maintenance

Sink is licensed AGPL-3.0. For a self-hosted internal shortener that you do not expose to third parties as a service, the practical effect is usually small, but the licence does reach network use: if you modify Sink and let users interact with it over a network, the AGPL's source-availability obligation is the part to read carefully. This is not legal advice, and if you plan to offer a shortened-link service to customers, have someone qualified read the licence against your deployment. On maintenance, the repository is not archived and the last push was on 2026-09-21, one day before this writing, so it is being worked on. Releases are more informative than the push date: v0.3.0 landed on 2026-07-18, v0.2.11 on 2026-07-12 and v0.2.10 on 2026-05-12, which is a cadence of roughly monthly to quarterly minor releases with patch releases in between. The upgrade path is mostly wrangler and D1 migrations: the deploy scripts run db:migrate:remote before deploying, and the Pages build runs migrations on master through postbuild, which is a pattern that will apply schema changes during a deploy whether or not you were watching. If you run Workers, you control when that happens; if you run the deprecated Pages path, you do not. Renovate is configured in the repository, so dependency bumps arrive as pull requests rather than surprise you at build time.

Editorial conclusion

Adopt Sink if you already run Cloudflare and want a self-hosted shortener with click analytics, custom slugs, device and country routing, and no server to patch. Do not adopt it if you need per-user accounts, a managed SLA or an on-premise deployment, which the README points at S.EE for. Before committing, verify that your Cloudflare plan covers D1 and Workers Analytics Engine in your region, and run the D1 migrations locally with wrangler d1 migrations apply sink --local to confirm the schema applies cleanly.

Frequently asked questions

Does Sink need a server or a database I have to manage?

No. Sink runs entirely on Cloudflare, with D1 as the authoritative link store, Workers KV as a write-through read cache and Workers Analytics Engine for analytics. You deploy it to Cloudflare Workers rather than provisioning a host.

How do I log in to a Sink instance I deployed myself?

The .env.example sets NUXT_SITE_TOKEN, described as required authentication, and the README's demo uses a Site Token to log in. The same value is the API key for the instance.

Can Sink run somewhere other than Cloudflare?

The README says Sink runs on Cloudflare's serverless platform and names Slite as the sibling version that runs as a local Node.js 24+ or Docker process with compatible features and API contracts. Neither is described as a legacy branch of the other.

Official sources

  1. License: AGPL-3.0
  2. miantiao-me/Sink on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/miantiao-me-sink.svg)](https://hysenlabs.com/projects/miantiao-me-sink)