Getting Started
Core Architecture
Link Engine
Analytics & Attribution
Partners & Affiliates
Third-Party Integrations
Identity & Security
Automation & Messaging
Developer Tools
The following files were used as context for generating this wiki page:
Dub relies on a multi-tiered caching and edge configuration architecture to minimize database load, maintain high availability during traffic spikes, and ensure ultra-low latency link resolution at the edge. By combining In-Memory LRU caches, Vercel runtime cache layers, and global Upstash Redis instances, the system optimizes read operations and handles failovers seamlessly when remote stores encounter disruptions. Sources: apps/web/lib/api/links/cache.ts:15-35, apps/web/lib/upstash/redis.ts:9-15
Beyond short link metadata, this infrastructure orchestrates domain lifecycle synchronization, edge-level rate limiting policies, background cache invalidation cron workers, and specialized auxiliary caches for authentication tokens, workspace flags, hostnames, and metatags. Sources: apps/web/lib/api/links/cache.ts:44-48, apps/web/app/ee/api/cron/domains/update/route.ts:98-106, apps/web/lib/upstash/ratelimit-policies.ts:18-24, apps/web/lib/auth/token-cache.ts:4-7
The Upstash Redis infrastructure establishes multiple client instances to isolate critical background operations from standard application traffic. Publicly exported connection handlers initialize clients using environment variables for REST endpoints and authentication tokens, supporting both standard database connections and dedicated global infrastructure layers. Sources: apps/web/lib/upstash/redis.ts:1-7, apps/web/lib/upstash/redis.ts:9-26
Client initialization checks for specialized global environment variables to determine whether operations should target a secondary Upstash Redis cluster. If UPSTASH_GLOBAL_REDIS_REST_URL and UPSTASH_GLOBAL_REDIS_REST_TOKEN are both present, redisConfig routes connections to the global cluster; otherwise, it falls back to the standard UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN.
Sources: apps/web/lib/upstash/redis.ts:12-23
Three primary Redis client instances are exported for application usage:
redis: Connects using standard primary environment variables (UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN).redisGlobal: Connects using redisConfig, targeting global infrastructure (such as linkCache and recordClick) so that transient cluster failures do not impact unrelated endpoints.redisGlobalWithTimeout: Extends redisConfig by injecting a signal method enforcing an AbortSignal.timeout(1500) constraint.
Sources: apps/web/lib/upstash/redis.ts:4-7, apps/web/lib/upstash/redis.ts:9-15, apps/web/lib/upstash/redis.ts:25-30Note
The redisGlobalWithTimeout client enforces a strict 1500ms timeout via AbortSignal.timeout(1500), making it suitable for latency-sensitive read paths like RecordClickCache.get().
Sources: apps/web/lib/upstash/redis.ts:27-30, apps/web/lib/api/links/record-click-cache.ts:28-32
The RecordClickCache class abstracts click deduplication and persistence through the global Redis clients. Click keys are formulated using domain, link key, and identity hash attributes.
Sources: apps/web/lib/api/links/record-click-cache.ts:6-11, apps/web/lib/api/links/record-click-cache.ts:34-36
When checking or recording a click ID, operations flow through specific methods in the cache layer:
RecordClickCache._createKey() formats the namespaced Redis key string: recordClick:${domain}:${key}:${identityHash}.
Sources: apps/web/lib/api/links/record-click-cache.ts:34-36RecordClickCache.set() invokes redisGlobal.set(), passing the generated key, clickId value, and an expiration option ex set to CACHE_EXPIRATION (60 seconds × 60 minutes = 3600 seconds).
Sources: apps/web/lib/api/links/record-click-cache.ts:4-5, apps/web/lib/api/links/record-click-cache.ts:13-26RecordClickCache.get() invokes redisGlobalWithTimeout.get<string>(), querying the store with the 1500ms abort signal boundary enforced.
Sources: apps/web/lib/api/links/record-click-cache.ts:28-32Sources: apps/web/lib/upstash/redis.ts:4-7, apps/web/lib/upstash/redis.ts:12-30, apps/web/lib/api/links/record-click-cache.ts:28-32
The LinkCache class and its supporting runtime structures manage short link metadata resolution, multi-tier caching, and fallback execution. To mitigate database load during traffic spikes and regional cold starts, short link lookups combine an in-memory LRUCache instance (bounded at 10,000 entries with a 5-second TTL), global Upstash Redis storage (redisGlobal and redisGlobalWithTimeout), Vercel runtime cache (vercelCache), and direct PlanetScale database queries via edge connectors (getLinkViaEdge).
Sources: apps/web/lib/api/links/cache.ts:8-27, apps/web/lib/api/links/cache.ts:71-136
When a link lookup is requested via LinkCache.get({ domain, key }), the execution traverses multiple cache tiers and fallback mechanisms before returning link metadata or throwing a 404 error.
Sources: apps/web/lib/api/links/cache.ts:71-136
LinkCache.get() constructs the namespace-aware key linkcache:${domain}:${key}.
Sources: apps/web/lib/api/links/cache.ts:78-78linkLRUCache.get(cacheKey). If found, it refreshes the LRU entry and returns immediately with redisFailOver: false.
Sources: apps/web/lib/api/links/cache.ts:81-87redisGlobalWithTimeout.get<RedisLinkProps>(cacheKey). If found, it populates the LRU cache and returns with redisFailOver: false.
Sources: apps/web/lib/api/links/cache.ts:93-98vercelCache.get(cacheKey). If present, it updates the LRU cache and returns with redisFailOver: true.
Sources: apps/web/lib/api/links/cache.ts:105-113getLinkViaEdge({ domain, key }). If no database record is found, it throws an explicit Error("Link not found."). Otherwise, it formats the link via formatRedisLink(), writes it to the LRU cache, registers a background write to Vercel cache using waitUntil(), and returns with redisFailOver: true.
Sources: apps/web/lib/api/links/cache.ts:117-134Warning
Because LRU caches are unshared across newly spun-up Fluid runtime instances during traffic surges, LinkCache falls back to vercelCache whenever global Redis is unavailable, preventing stampedes on the underlying database.
Sources: apps/web/lib/api/links/cache.ts:23-27, apps/web/lib/api/links/cache.ts:105-113
LinkCache provides batch and single-item modification routines that synchronize Redis pipelines, LRU entries, and Next.js cache tags.
Sources: apps/web/lib/api/links/cache.ts:33-70, apps/web/lib/api/links/cache.ts:138-171
Tip
The _createKey() helper checks domain case-sensitivity using isCaseSensitiveDomain(domain), decoding case-sensitive keys or normalizing case-insensitive keys to lowercase to maintain consistent cache namespaces.
Sources: apps/web/lib/api/links/cache.ts:173-179
Sources: apps/web/lib/api/links/cache.ts:33-48, apps/web/lib/api/links/cache.ts:71-136, apps/web/lib/api/links/cache.ts:144-171, apps/web/lib/planetscale/get-link-via-edge.ts:41-68
The Edge Middleware resolves incoming link requests by evaluating domain case sensitivity, normalization rules, and cache presence before falling back to direct edge database execution. LinkMiddleware parses the request URL to extract the domain and original key, normalizes keys to lowercase for case-insensitive domains or punycode-encodes them, strips inspect mode suffixes (+), and assigns root domain links to _root.
Sources: apps/web/lib/middleware/link.ts:43-67
When an incoming request hits the middleware, resolution proceeds through a strict sequence of lookups and fallbacks:
LinkMiddleware calls linkCache.get({ domain, key }) to fetch cached metadata and verify redisFailOver status.cachedLink is absent, it executes getLinkViaEdge({ domain, key }).getLinkViaEdge checks inFlightLinkLookups Map; if a pending promise exists for the lookup key (domain:key), it awaits that promise. Otherwise, it invokes getLinkViaEdgeHelper.getLinkViaEdgeHelper prepares the query using isCaseSensitiveDomain(domain) to decide whether to encode via encodeKey or decode and punycode-encode via punyEncode(safeDecodeURIComponent(key)), then queries the database via conn.execute.buff.ly, control falls back to crawlBitly(req, ev) which queries the Bitly API, creates a database link via Prisma, records the link, and issues a redirect.
Sources: apps/web/lib/middleware/link.ts:89-117, apps/web/lib/planetscale/get-link-via-edge.ts:10-68, apps/web/lib/middleware/utils/crawl-bitly.ts:20-82When processing valid links, the middleware evaluates whether to cache click identifiers and record click metadata based on conversion tracking flags, partner status, and tracking URL signatures. Sources: apps/web/lib/middleware/link.ts:175-184
Warning
During Redis failover states, click tracking routines are explicitly skipped to prevent request timeouts, and cookie lookup or minting for dubIdCookieName is bypassed entirely.
Sources: apps/web/lib/middleware/link.ts:93-98, apps/web/lib/middleware/link.ts:193-211
Domain configurations manage routing, branding, and asset settings across workspaces while synchronizing records between primary database stores, Vercel deployments, and edge memory caches. When a domain is created or modified, API route handlers validate constraints, apply plan tier checks, and push configuration changes to external services before updating persistent storage. Sources: apps/web/app/api/domains/route.ts:96-173, apps/web/app/api/domains/domain/route.ts:45-64
To optimize edge execution performance without repeated round-trips to the primary database, domain lookups at the edge leverage an in-memory Least Recently Used (LRU) cache.
const domainLRUCache = new LRUCache<string, EdgeDomainProps>({
max: 1000,
ttl: 5 * 60 * 1000, // 5 minutes
});
export const getDomainViaEdge = async (domain: string) => {
const cached = domainLRUCache.get(domain);
if (cached) {
return cached;
}
const { rows } =
(await conn.execute<EdgeDomainProps>(
"SELECT * FROM Domain WHERE slug = ?",
[domain],
)) || {};
const result =
rows && Array.isArray(rows) && rows.length > 0 ? rows[0] : null;
if (result !== null) {
domainLRUCache.set(domain, result);
}
return result;
};Domain registration invokes environment-specific synchronization with Vercel and manages bulk link transfers during workspace imports.
When a domain is added via the API, the system checks the VERCEL environment variable and registers the domain against Vercel infrastructure, optionally ignoring domain_already_in_use responses. During short.io imports, unmanaged source domains are automatically provisioned in the workspace database, synced with Vercel, and initialized with root records in a transaction block.
Sources: apps/web/app/api/domains/route.ts:163-173, apps/web/app/api/workspaces/idOrSlug/import/short/route.ts:92-125
Warning
When adding domains programmatically, failure to handle Vercel synchronization errors other than domain_already_in_use will cause the creation request to abort with an HTTP 422 status response.
Sources: apps/web/app/api/domains/route.ts:167-172
The rate limiting infrastructure uses Upstash Redis to enforce endpoint-specific consumption policies across authentication, upload, file transfer, and domain management workflows. Policies are typed using RatelimitPolicy definitions and referenced by name across application routes.
Sources: apps/web/lib/upstash/ratelimit-policies.ts:1-16, apps/web/lib/upstash/index.ts:1-5
The RATELIMIT_POLICIES constant maps policy identifiers to specific attempt thresholds, time windows, key prefixes, and custom violation error messages.
Note
Policies such as socialAccountVerification are explicitly shared between start and verification routes so that both actions count toward a single unified platform budget.
Sources: apps/web/lib/upstash/ratelimit-policies.ts:173-179
Cache invalidation and background synchronization are managed through dedicated cron endpoints and background job processors that purge stale cache keys, synchronize Redis state resources, and process high-volume event streams. Operations such as domain updates, partner data modifications, and discount adjustments rely on batched routines to expire associated short links and maintain consistency between persistent storage and edge caches. Sources: apps/web/app/ee/api/cron/domains/update/route.ts:1-121, apps/web/app/ee/api/cron/sync-redis-resources/route.ts:1-42, apps/web/lib/jobs/handlers/invalidate-links-for-discounts-job.ts:1-210
Background maintenance tasks execute on regular intervals, utilizing QStash signature verification and concurrency protection locks. The sync scheduler orchestrates Redis resource synchronization across workspace integrations and webhook sets. Sources: apps/web/app/ee/api/cron/sync-redis-resources/route.ts:1-42, apps/web/app/ee/api/cron/streams/update-click-stats/route.ts:1-21
Sources: apps/web/app/ee/api/cron/sync-redis-resources/route.ts:1-42, apps/web/app/ee/api/cron/cleanup/link-retention/route.ts:1-124, apps/web/app/ee/api/cron/links/invalidate-for-partners/route.ts:1-54, apps/web/lib/jobs/handlers/invalidate-links-for-discounts-job.ts:1-210
When migrating links between domains via /api/cron/domains/update, processing occurs through a batched lifecycle workflow to prevent timeout and memory exhaustion.
Warning
Background update routines such as updateShortLinks and cache expirations rely on Promise.allSettled or chunked iteration to ensure partial network failures in external services do not halt database cursor progression or leave pagination cursors stranded.
Sources: apps/web/app/ee/api/cron/domains/update/route.ts:98-105, apps/web/lib/jobs/handlers/invalidate-links-for-discounts-job.ts:204-206
Specialized caching mechanisms support auxiliary workflows across the platform, including restricted authentication tokens, workspace product flags, hostnames, metatags, reward versions, and onboarding states. These stores use Upstash Redis pipelines and key-value methods to maintain TTLs and minimize database pressure for frequently queried auxiliary entities. Sources: apps/web/lib/analytics/allowed-hostnames-cache.ts:1-53, apps/web/lib/api/workspaces/workspace-product-cache.ts:1-27, apps/web/lib/auth/token-cache.ts:1-76, apps/web/lib/api/rewards/reward-version.ts:1-50, apps/web/lib/upstash/record-metatags.ts:1-20
Sources: apps/web/lib/analytics/allowed-hostnames-cache.ts:3-4, apps/web/lib/api/workspaces/workspace-product-cache.ts:4-5, apps/web/lib/auth/token-cache.ts:4-5, apps/web/lib/api/rewards/reward-version.ts:4-14, apps/web/app/api/domains/client/saved/route.ts:20-25
Restricted and legacy personal tokens use TokenCache to serialize parsed token records against hashed keys, executing batch expirations by forcing TTL reductions down to 1 second via Redis pipelines. Metatag generation metrics and failures are tracked using Upstash sorted set increments via recordMetatags, routing errors to metatags-error-zset and successful domain generations to metatags-zset.
Sources: apps/web/lib/auth/token-cache.ts:31-69, apps/web/lib/upstash/record-metatags.ts:8-20
Note
When executing AllowedHostnamesCache.mset or TokenCache.expireMany, empty input arrays short-circuit execution without invoking redis pipeline instructions.
Sources: apps/web/lib/analytics/allowed-hostnames-cache.ts:14-16, apps/web/lib/auth/token-cache.ts:57-60