rdyrct
Encurtador de URL e gerador de QR de código aberto com links personalizados, análises de campanha, equipes, API e acesso MCP.
Documentação
rdyrct
Encurtador de links baseado em organizações + QR codes, rodando inteiramente na borda da Cloudflare.
Implante com um clique
Isso provisiona o Worker a partir do painel da Cloudflare. Ainda não é uma instalação completa por si só: você ainda precisa definir APP_URL, APP_HOST e os segredos, aplicar as migrações e anexar um domínio. Siga Implantar no Cloudflare Workers (rastreado em #280).
Recursos
- Links curtos com slugs personalizados ou aleatórios, parâmetros UTM opcionais (source, medium, campaign, term, content) aplicados no momento do redirecionamento.
- Endereços de link agrupados: renomear um link mantém o slug antigo redirecionando por 48 horas antes de ser liberado, e um link pode carregar aliases extras que todos redirecionam para o mesmo destino.
- QR codes por link (planos pagos), com logotipo opcional no centro, pré-visualização ao vivo e download em PNG/SVG.
- Domínios personalizados (Pro): conecte
links.yourbrand.comvia Cloudflare for SaaS, com slugs com namespace por domínio. - Organizações com funções
owner/admin/member: administradores gerenciam a equipe, domínios e cobrança; membros gerenciam links. - Convites por magic-link: uso único, válidos por 7 dias, enviados por e-mail.
- Análises que respeitam a privacidade: cliques por dia, principais links, países, referenciadores, dispositivos. Endereços IP nunca são armazenados.
- Cobrança por usuário Free/Hobby/Pro através do Polar, que atua como Merchant of Record e lida com impostos/IVA.
- Console de superadministrador fixado por segredo: a conta correspondente a
SUPERADMIN_EMAILrecebe um painel de console na nuvem para uso, detalhamento de organizações e gerenciamento de planos. Ele retorna 404 para todos os outros.
Stack
rdyrct é construído Cloudflare-first: todo o produto roda em um único Worker sem backend separado para hospedar:
- Cloudflare Workers: um Worker atende a API, o caminho quente de redirecionamento e o aplicativo React estático.
- D1: SQLite na borda, fonte da verdade para autenticação, organizações, links, cliques e domínios.
- KV: consultas slug → destino no caminho quente de redirecionamento, para que um clique nunca espere no D1.
- R2: imagens de logotipo de QR, chaveadas por organização, servidas imutáveis e em cache.
- Cloudflare for SaaS: hostnames personalizados para os próprios domínios de links curtos das organizações Pro.
- Workers Rate Limiting: controles de abuso separados para autenticação, e-mail, gravações, uploads, domínios, cobrança e registro de cliques.
Camada de aplicação:
- Hono para roteamento
- BetterAuth para autenticação por e-mail+senha, com verificação por OTP por e-mail e hash PBKDF2/WebCrypto
- Drizzle ORM sobre D1
- React 19 + Vite +
@cloudflare/vite-plugin - Tailwind v4 + Base UI
- TanStack Query + React Router
- Polar para cobrança, Resend para e-mail transacional
Desenvolvimento local
bun install
cp .dev.vars.example .dev.vars # then edit; at minimum set SUPERADMIN_EMAIL
bunx emulate --service resend # local Resend inbox on :4000
bun run db:migrate:local
bun run dev # https://rdyrct.localhost
-
Cadastre-se com o endereço que você definiu como
SUPERADMIN_EMAILpara desbloquear o console de administrador; qualquer outro endereço é um usuário normal. -
Novas contas veem um prompt de criação de organização em páginas com escopo de organização até criarem sua primeira organização.
-
Os códigos de verificação de e-mail são enviados através do emulador do Resend do emulate.dev em vez de uma caixa de entrada real. Leia com:
curl localhost:4000/emails -H 'authorization: Bearer test_token_admin'e copie o código de 6 dígitos da mensagem mais recente.
-
Definir
CF_DEV_ENV=simulated(o padrão.dev.vars.example) simula a API de Custom Hostnames da Cloudflare localmente: o DNS fica pronto após cerca de 3 segundos e o TLS após cerca de 12 segundos, então "Verificar status" mostra a mesma progressão sem uma zona real.CF_DEV_ENV=instantrelata ambos prontos de uma vez, que é o que a execução e2e usa para não ficar esperando o temporizador. Sem nenhum dos dois, a falta deCF_API_TOKEN/CF_ZONE_IDfalha de forma fechada em vez de fingir uma resposta. -
A cobrança contra uma conta real do Polar sandbox precisa de uma URL pública (
wrangler dev --remoteou um túnel) para que os webhooks alcancem/api/webhooks/polar.
Testes
bun run test # fast unit tests
bun run test:worker # Worker routes with local D1 and KV bindings
bun run e2e:install # install Chromium for Playwright once
bun run e2e:smoke # real-browser baseline regression checks
bun run verify # types, lint, formatting, unit, and Worker tests
O Playwright é dono do ciclo de vida do servidor local e nunca redefine ou semeia o estado local da Cloudflare. Testes de navegador com falha mantêm uma captura de tela e um trace em test-results/.
Implantar no Cloudflare Workers
Mínimo para rodar
- Um domínio na Cloudflare (ou o endereço gratuito
*.workers.dev, para testar primeiro). - Dois segredos:
BETTER_AUTH_SECRET(qualquer string longa aleatória) eSUPERADMIN_EMAIL(a conta que se torna o administrador da plataforma). - Os recursos abaixo, depois
bun run db:migrate:remote && bun run deploy.
Todo o resto é opcional. Cada peça diz o que adiciona:
| Configuração | Adiciona |
|---|---|
RESEND_API_KEY, MAIL_FROM | E-mail: códigos de verificação de cadastro, redefinição de senha, convites, avisos. Sem isso, o cadastro pula a verificação (veja abaixo). |
POLAR_* | Venda dos planos Hobby e Pro. Sem isso, ninguém é limitado e a cobrança fica oculta. |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | "Continuar com Google". |
CF_API_TOKEN, CF_ZONE_ID | Domínios próprios dos clientes através do Cloudflare for SaaS. |
SHARED_LINK_HOST | Um segundo host mais curto para novos links de domínio compartilhado. |
CAP_SECRET | Prova de trabalho na frente do cadastro e da redefinição de senha. |
SENTRY_DSN | Relatórios de erro para o seu projeto Sentry. |
Sem provedor de e-mail e sem login do Google, as contas não são verificadas e as senhas não podem ser redefinidas. Isso é aceitável para uma instância privada e não recomendado para uma pública. O console de administrador avisa enquanto isso for verdade.
Criar os recursos
bunx wrangler kv namespace create LINKS
bunx wrangler d1 create rdyrct
bunx wrangler r2 bucket create rdyrct-media
bunx wrangler queues create rdyrct-storage
bunx wrangler queues create rdyrct-storage-dlq
Cole os ids retornados em wrangler.jsonc:
kv_namespaces[0].idd1_databases[0].database_id
O nome do bucket R2 em wrangler.jsonc é rdyrct-qr-logos. Ou crie esse nome, ou altere r2_buckets[0].bucket_name para o que você criou.
Definir as variáveis e segredos
Em wrangler.jsonc, defina APP_URL (por exemplo https://links.example.com, sem barra final) e APP_HOST (links.example.com). O arquivo já vem com os valores de rdyrct.com para o resto (MAIL_FROM, POLAR_*, CF_ZONE_ID, SENTRY_DSN): limpe ou substitua cada um que você não usar.
Defina os segredos. Ou um por um:
bunx wrangler secret put BETTER_AUTH_SECRET
bunx wrangler secret put SUPERADMIN_EMAIL
ou em massa com cp prod.secrets.env.example prod.secrets.env, preencha e depois:
bunx wrangler secret bulk prod.secrets.env && rm prod.secrets.env
Então migre e envie:
bun run db:migrate:remote
bun run deploy
Anexe seu domínio
Painel da Cloudflare → Workers → seu worker → Settings → Domains & Routes → Add → Custom domain. Links curtos ficam na raiz (https://links.example.com/<slug>); o aplicativo é servido em todos os outros caminhos. Mantenha rotas fora de wrangler.jsonc: uma lista routes substitui o que estiver anexado no painel, e enviar a sua entregaria para cada fork. A rota */* é apenas para os domínios próprios dos clientes (abaixo).
E-mail (opcional)
Defina RESEND_API_KEY e MAIL_FROM (um remetente em um domínio que você verificou no Resend). Sem eles, nenhum e-mail sai: o cadastro pula o código de verificação e autentica o usuário, e o formulário de redefinição de senha diz que precisa de um provedor de e-mail. O envio vive em uma função, sendEmail() em src/worker/email.ts, então outro provedor é uma mudança ali.
Vender planos (opcional)
Pule esta seção para rodar sem limites e sem cobrança. Para vender planos, defina POLAR_SERVER=sandbox (ou production quando estiver ao vivo), POLAR_PRO_PRODUCT_ID e POLAR_HOBBY_PRODUCT_ID (produtos recorrentes que você cria no Polar), e os segredos POLAR_ACCESS_TOKEN e POLAR_WEBHOOK_SECRET.
Configuração do Polar: crie um Organization Access Token com escopos checkouts:write e customer_sessions:write. Adicione um endpoint de webhook em https://rdyrct.com/api/webhooks/polar e assine todos os cinco eventos que o Worker trata. Se faltar um, o estado de cobrança fica dessincronizado:
| Evento | O que faz |
|---|---|
subscription.active | Inicia a assinatura: plano, cliente, preço e acesso |
subscription.updated | O snapshot completo da assinatura: status, produto, estado de cancelamento, fim do período |
subscription.canceled | Marca como terminando no fim do período; o acesso continua até lá |
subscription.uncanceled | Limpa um cancelamento agendado |
subscription.revoked | Encerra: a assinatura e seu acesso desaparecem |
Quais status de assinatura mantêm acesso é uma decisão, em src/worker/entitlement.ts. active, trialing e past_due mantêm, porque o Polar tenta novamente um pagamento com falha por dias e envia subscription.revoked quando desiste. unpaid, canceled, incomplete e incomplete_expired não mantêm, e um status sem regra não mantém nada.
Acesso pago que um administrador concede manualmente é um comp, armazenado separado da assinatura e nunca contado como assinante. Um comp tem precedência sobre a assinatura abaixo dele, então revogar uma assinatura não pode tirar acesso que um administrador deu.
O console de administrador relata contagens, nunca dinheiro: quantas pessoas têm um plano pago, quantas pagam por ele e quantas são comped. Quem está cancelando aparece na própria linha na lista de usuários, onde nomeia alguém. A receita é lida no painel do Polar, que sabe o que cobrou líquido de descontos, impostos e reembolsos.
Segundo host de domínio compartilhado (opcional). Anexe um domínio mais curto da mesma forma, verifique que https://<it>/ responde com um redirecionamento, e só então ative com bunx wrangler secret put SHARED_LINK_HOST. Ele entra em vigor sem deploy, e bunx wrangler secret delete SHARED_LINK_HOST desliga. Nessa ordem porque cada novo link começa a usá-lo imediatamente: definido antes do domínio resolver, ele entrega links que não levam a lugar nenhum. Links antigos continuam funcionando em ambos os hosts, já que a chave KV de um slug de domínio compartilhado não tem host. Sem definir, links compartilhados usam APP_HOST.
Revise as políticas de rate limiting, monitoramento, regra de WAF e etapas de rollback antes do primeiro deploy de produção.
Revise a arquitetura, fluxos de dados e prioridades de melhoria antes de alterar fluxos de exclusão de link, domínio, logotipo ou organização.
Domínios personalizados do cliente (Pro): para permitir que organizações usem links.theirbrand.com, habilite Cloudflare for SaaS na zona rdyrct.com, crie uma origem fallback sem origem e com proxy (ex.: fallback.rdyrct.com AAAA 100::), e adicione uma rota de Worker */* apontando para o worker rdyrct. O CF_API_TOKEN precisa da permissão Zone → SSL and Certificates → Edit com escopo na zona. Também verifique seu domínio de envio (ex.: mail.rdyrct.com) no Resend para que e-mails transacionais não sejam bloqueados.
Configuração
| Nome | Tipo | Finalidade |
|---|---|---|
BETTER_AUTH_SECRET | segredo | Assina sessões e tokens |
SUPERADMIN_EMAIL | segredo | A conta que se torna o administrador da plataforma |
RESEND_API_KEY | segredo | Chave da API Resend para e-mail transacional |
POLAR_ACCESS_TOKEN | segredo | Token de acesso à API Polar (escopos: checkouts:write, customer_sessions:write) |
POLAR_WEBHOOK_SECRET | segredo | Verifica assinaturas de webhooks do Polar; endpoint https://rdyrct.com/api/webhooks/polar |
CF_API_TOKEN | segredo | Token Cloudflare com Zone → SSL and Certificates → Edit (domínios personalizados) |
APP_URL | var | URL pública completa do aplicativo, ex.: https://rdyrct.com |
APP_HOST | var | Host público, ex.: rdyrct.com |
SHARED_LINK_HOST | segredo (opc.) | Segundo host de domínio compartilhado para novos links, ex.: rdyr.cc; defina após ele resolver |
MAIL_FROM | var | Endereço de remetente para e-mail de saída |
RESEND_BASE_URL | var (somente dev) | Aponta para o emulador local do Resend |
POLAR_SERVER | var | sandbox ou production |
POLAR_PRO_PRODUCT_ID | var | ID do produto Polar para o plano Pro |
POLAR_HOBBY_PRODUCT_ID | var | ID do produto Polar para o plano Hobby |
CF_ZONE_ID | var | ID da zona usada para Custom Hostnames |
CF_DEV_ENV | var (somente dev) | simulated simula a API de Custom Hostnames em um temporizador, instant sem um |
Os segredos são definidos com wrangler secret put NAME ou wrangler secret bulk prod.secrets.env em produção; localmente, todos vêm de .dev.vars (veja .dev.vars.example).
Estrutura do projeto
migrations/ D1 schema (numbered SQL migrations, applied in order)
scripts/ Local dev utilities (e.g. seed-local.ts)
src/worker/ Hono API, BetterAuth, KV publishing, redirect hot path
routes/ auth/user, orgs, links, qr-logos, domains, billing, admin
better-auth.ts plan.ts util.ts guards.ts org-role.ts rate-limit.ts session.ts
email.ts password.ts kv.ts storage.ts alerts.ts clicks.ts workflows.ts
src/shared/types.ts DTOs + PLAN_LIMITS (shared worker ↔ app)
src/app/ React SPA
routes/ page-level route components
ui/ design-system primitives
components/ feature components
lib/ client utilities, API hooks
.agents/skills/ Agent skills (react-doctor)
.claude/skills/ Claude skills (fallow, react-doctor)
Licença
MIT © Andrea Bruno. Veja LICENSE.