<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Documentation on vanityURLs</title><link>https://www.vanityurls.link/en/docs/</link><description>Recent content in Documentation on vanityURLs</description><generator>Hugo</generator><language>en-CA</language><atom:link href="https://www.vanityurls.link/en/docs/index.xml" rel="self" type="application/rss+xml"/><item><title>Admin dashboard</title><link>https://www.vanityurls.link/en/docs/admin-dashboard/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/admin-dashboard/</guid><description>&lt;p&gt;The admin dashboard is intentionally read-only. It is an operational view over the generated registry, not a content management system.&lt;/p&gt;
&lt;p&gt;It reads:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/v8s.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="what-it-shows"&gt;What it shows&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Route inventory&lt;/li&gt;
&lt;li&gt;Effective state&lt;/li&gt;
&lt;li&gt;Exact vs splat routing&lt;/li&gt;
&lt;li&gt;Expiry review&lt;/li&gt;
&lt;li&gt;Metadata quality&lt;/li&gt;
&lt;li&gt;Routing-table behavior&lt;/li&gt;
&lt;li&gt;Expiring-soon and missing-metadata filters&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Analytics remain in Umami or Fathom. The dashboard is the routing and lifecycle plane; analytics tools are the measurement plane.&lt;/p&gt;
&lt;h2 id="protection"&gt;Protection&lt;/h2&gt;
&lt;p&gt;Protect &lt;code&gt;/_stats&lt;/code&gt;, &lt;code&gt;/_stats/*&lt;/code&gt;, &lt;code&gt;/_tests&lt;/code&gt;, and &lt;code&gt;/_tests/*&lt;/code&gt; with Cloudflare Access. The Worker validates the Access assertion header and fails closed when protection is incomplete.&lt;/p&gt;</description></item><item><title>Blocklist policy</title><link>https://www.vanityurls.link/en/docs/blocklist/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/blocklist/</guid><description>&lt;p&gt;vanityURLs uses &lt;code&gt;defaults/v8s-blocklist.json&lt;/code&gt; as the upstream trust-and-safety policy. Instance-specific policy lives in &lt;code&gt;custom/v8s-blocklist.json&lt;/code&gt; and is merged over the defaults.&lt;/p&gt;
&lt;p&gt;The goal is to protect the reputation of a short-link domain by reducing phishing, malware, redirect chains, and risky URL forms.&lt;/p&gt;
&lt;p&gt;A redirect engine is powerful infrastructure. Do not use a vanityURLs instance to hide malicious destinations, bypass trust systems, launder another shortener chain, disguise affiliate or tracking links without disclosure, or route people to content they did not reasonably expect. A short-link domain earns trust slowly and can lose it very quickly.&lt;/p&gt;</description></item><item><title>CLI</title><link>https://www.vanityurls.link/en/docs/cli/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/cli/</guid><description>&lt;p&gt;The repo-local CLI is &lt;code&gt;./scripts/lnk&lt;/code&gt;. It is a Node executable, so it works on macOS, Linux, Windows, and CI environments where Node and Git are available.&lt;/p&gt;
&lt;p&gt;Bash is not required for link management. The Zsh helper remains optional and separate.&lt;/p&gt;
&lt;h2 id="requirements"&gt;Requirements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 20 or newer&lt;/li&gt;
&lt;li&gt;npm&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;Wrangler for deployment commands&lt;/li&gt;
&lt;li&gt;A Cloudflare account only when deploying or managing Worker secrets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On Windows, run the commands from PowerShell, Windows Terminal, or a Git-aware shell. The CLI itself does not require WSL.&lt;/p&gt;</description></item><item><title>Cloudflare Workers</title><link>https://www.vanityurls.link/en/docs/cloudflare/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/cloudflare/</guid><description>&lt;p&gt;The current vanityURLs runtime deploys as a Cloudflare Worker with static assets. The Worker is the origin for the short-link hostname, so use a Worker Custom Domain instead of the older Pages &lt;code&gt;_redirects&lt;/code&gt; or DNS &lt;code&gt;AAAA 100::&lt;/code&gt; route pattern.&lt;/p&gt;
&lt;h2 id="cloudflare-navigation-map"&gt;Cloudflare navigation map&lt;/h2&gt;
&lt;p&gt;Cloudflare splits the required vanityURLs settings across three different dashboard areas. Check the dashboard scope before changing a setting; being in the right Cloudflare product is not always enough.&lt;/p&gt;</description></item><item><title>Custom overrides</title><link>https://www.vanityurls.link/en/docs/custom-overrides/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/custom-overrides/</guid><description>&lt;p&gt;Use &lt;code&gt;custom/&lt;/code&gt; for instance-owned files. This keeps v8s.link-style deployments upgradable because default pages, Worker logic, and product policy can move forward without mixing in every local brand choice.&lt;/p&gt;
&lt;h2 id="build-order"&gt;Build order&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Copy &lt;code&gt;defaults/public/&lt;/code&gt; into &lt;code&gt;build/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Overlay &lt;code&gt;custom/public/&lt;/code&gt; when it exists&lt;/li&gt;
&lt;li&gt;Copy the default &lt;code&gt;defaults/public/_stats/index.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Overlay &lt;code&gt;custom/public/_stats/index.html&lt;/code&gt; when it exists&lt;/li&gt;
&lt;li&gt;Build &lt;code&gt;v8s.json&lt;/code&gt; from &lt;code&gt;custom/v8s-links.txt&lt;/code&gt; when it exists, otherwise from &lt;code&gt;defaults/v8s-links.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Merge &lt;code&gt;defaults/v8s-blocklist.json&lt;/code&gt;, optional &lt;code&gt;custom/v8s-blocklist.json&lt;/code&gt;, and optional generated blocklist data&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="recommended-custom-files"&gt;Recommended custom files&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;custom/v8s-links.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;custom/v8s-schedules.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;custom/v8s-blocklist.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;custom/public/v8s-logo.svg
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;custom/public/v8s-redirected.svg
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;custom/public/favicon.svg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Only add HTML or CSS overrides when brand assets and content files are not enough.&lt;/p&gt;</description></item><item><title>Link format</title><link>https://www.vanityurls.link/en/docs/link-format/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/link-format/</guid><description>&lt;p&gt;&lt;code&gt;v8s-links.txt&lt;/code&gt; is the human-authored source of truth for links. Each non-empty, non-comment row is pipe-delimited:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;slug|target|state|title|description|tags|owner|expires_at|notes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Field&lt;/th&gt;
 &lt;th style="text-align: right"&gt;Required&lt;/th&gt;
 &lt;th&gt;Description&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;slug&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;yes&lt;/td&gt;
 &lt;td&gt;Slash-delimited alias path, without leading &lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;target&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;yes&lt;/td&gt;
 &lt;td&gt;Absolute &lt;code&gt;http&lt;/code&gt; or &lt;code&gt;https&lt;/code&gt; URL, or a hostname normalized to &lt;code&gt;https://&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;state&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;no&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;permanent&lt;/code&gt;, &lt;code&gt;ephemeral&lt;/code&gt;, &lt;code&gt;expired&lt;/code&gt;, &lt;code&gt;disabled&lt;/code&gt;, &lt;code&gt;maintenance&lt;/code&gt;, &lt;code&gt;deactivated&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;no&lt;/td&gt;
 &lt;td&gt;Dashboard title&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;no&lt;/td&gt;
 &lt;td&gt;Human-readable purpose&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tags&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;no&lt;/td&gt;
 &lt;td&gt;Comma-separated tags&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;owner&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;recommended&lt;/td&gt;
 &lt;td&gt;Accountability label&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;expires_at&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;no&lt;/td&gt;
 &lt;td&gt;ISO date or timestamp&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;notes&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;no&lt;/td&gt;
 &lt;td&gt;Internal notes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="exact-links"&gt;Exact links&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;social/x|https://x.com/vanityURLs|permanent|X / Twitter|Social profile|social,x|v8s||
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The link resolves only &lt;code&gt;/social/x&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Migration guide</title><link>https://www.vanityurls.link/en/docs/migration/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/migration/</guid><description>&lt;p&gt;Use this guide when moving from the older Cloudflare Pages &lt;code&gt;_redirects&lt;/code&gt; model to the current Worker model used by v8s.link.&lt;/p&gt;
&lt;h2 id="what-changed"&gt;What changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wrangler.toml&lt;/code&gt; is the deployment source of truth&lt;/li&gt;
&lt;li&gt;Static files are served through the Worker assets binding named &lt;code&gt;ASSETS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The build copies &lt;code&gt;defaults/&lt;/code&gt;, overlays &lt;code&gt;custom/&lt;/code&gt;, and generates &lt;code&gt;build/v8s.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;custom/v8s-links.txt&lt;/code&gt; is preferred when it exists; otherwise the build uses &lt;code&gt;defaults/v8s-links.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/_stats&lt;/code&gt; and &lt;code&gt;/_tests&lt;/code&gt; are protected by Cloudflare Access&lt;/li&gt;
&lt;li&gt;Server-side analytics are emitted by the Worker&lt;/li&gt;
&lt;li&gt;Scanner probes and risky destinations are blocked by &lt;code&gt;v8s-blocklist.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="convert-legacy-lnk-files"&gt;Convert legacy .lnk files&lt;/h2&gt;
&lt;p&gt;Legacy rows looked like this:&lt;/p&gt;</description></item><item><title>Quickstart</title><link>https://www.vanityurls.link/en/docs/getting-started/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/getting-started/</guid><description>&lt;p&gt;vanityURLs is a Git-managed short-link engine for your own domain. The current runtime deploys as a Cloudflare Worker with static assets. The build starts from &lt;code&gt;defaults/&lt;/code&gt;, overlays your &lt;code&gt;custom/&lt;/code&gt; files, generates &lt;code&gt;build/v8s.json&lt;/code&gt;, and publishes the Worker with Wrangler.&lt;/p&gt;
&lt;h2 id="what-you-need"&gt;What you need&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A GitHub repository based on &lt;code&gt;vanityURLs/vanityURLs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A short domain, such as the public reference domain &lt;code&gt;v8s.link&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A Cloudflare account with the domain in Cloudflare DNS&lt;/li&gt;
&lt;li&gt;Node.js 20 or newer and npm&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;Wrangler connected to the Cloudflare account that owns the Worker&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The primary CLI is &lt;code&gt;./scripts/lnk&lt;/code&gt;, a Node script that runs on macOS, Linux, Windows, and CI environments where Node and Git are available. Bash is not required for link, schedule, or blocklist management. The optional &lt;code&gt;scripts/v8s.zsh&lt;/code&gt; helper is only for Zsh users who want to open known redirects from their shell.&lt;/p&gt;</description></item><item><title>Release checklist</title><link>https://www.vanityurls.link/en/docs/release-checklist/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/release-checklist/</guid><description>&lt;p&gt;Use this checklist before launching a new instance or promoting a major upgrade. A v8s instance is simple, but a short-link domain still attracts scanners, bots, and abuse attempts.&lt;/p&gt;
&lt;h2 id="repository"&gt;Repository&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;npm run clean&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Refresh upstream &lt;code&gt;defaults/&lt;/code&gt; and &lt;code&gt;scripts/&lt;/code&gt; with the upgrade workflow.&lt;/li&gt;
&lt;li&gt;Keep all instance-owned files in &lt;code&gt;custom/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npm run check&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Review generated registry and blocklist changes.&lt;/li&gt;
&lt;li&gt;Commit and deploy from a clean working tree.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="worker-and-assets"&gt;Worker and assets&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Confirm the Worker has an &lt;code&gt;ASSETS&lt;/code&gt; binding.&lt;/li&gt;
&lt;li&gt;Confirm the custom domain points to the Worker.&lt;/li&gt;
&lt;li&gt;Disable public &lt;code&gt;workers.dev&lt;/code&gt; and preview URLs if they are not part of the service.&lt;/li&gt;
&lt;li&gt;Protect &lt;code&gt;/_stats&lt;/code&gt; and &lt;code&gt;/_tests&lt;/code&gt; with Cloudflare Access.&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;/_stats/*&lt;/code&gt; and &lt;code&gt;/_tests/*&lt;/code&gt; policies narrow.&lt;/li&gt;
&lt;li&gt;Confirm the Worker accepts only &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;HEAD&lt;/code&gt;, and &lt;code&gt;OPTIONS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Confirm private implementation assets return 404.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cloudflare-domain-configuration"&gt;Cloudflare domain configuration&lt;/h2&gt;
&lt;p&gt;In Cloudflare, the relevant settings are split across three places:&lt;/p&gt;</description></item><item><title>Repository layout</title><link>https://www.vanityurls.link/en/docs/repository-layout/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/repository-layout/</guid><description>&lt;p&gt;The repository separates product defaults from instance-owned changes. That separation is what makes v8s.link useful as a reference implementation and lets future instances update upstream code without losing local branding.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;defaults/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public/ # default HTML, CSS, icons, localized pages
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public/_stats/ # read-only stats dashboard shell
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v8s-links.txt # demo/default link registry
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v8s-schedules.json # optional schedule rules
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v8s-blocklist.json # default trust-and-safety policy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v8s-blocklist-categories.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;custom/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public/ # instance branding and page overrides
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v8s-links.txt # instance-owned links
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v8s-schedules.json # instance schedules
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v8s-blocklist.json # instance allow/block policy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;scripts/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; src/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; worker.mjs # canonical Worker runtime source
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; worker.test.mjs # Worker runtime tests
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; lnk # Node CLI for links and schedules
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; build.mjs # build defaults + custom into deploy output
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; clean.mjs # remove generated output
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; upgrade.mjs # refresh product-owned files from upstream
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;build/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v8s.json # generated runtime registry
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v8s-blocklist.json # generated trust-and-safety policy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ...static assets...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="defaults"&gt;Defaults&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;defaults/&lt;/code&gt; is the product baseline. The public v8s.link instance uses these files to show a working shortener with the search-style home page, expand page, localized status pages, default icons, the protected stats shell, and sample links.&lt;/p&gt;</description></item><item><title>Runtime registry</title><link>https://www.vanityurls.link/en/docs/runtime-registry/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/runtime-registry/</guid><description>&lt;p&gt;The Worker does not read &lt;code&gt;v8s-links.txt&lt;/code&gt; on every request. The build creates a private runtime registry at &lt;code&gt;build/v8s.json&lt;/code&gt; and publishes it as an internal asset. Direct public requests for &lt;code&gt;v8s.json&lt;/code&gt;, &lt;code&gt;redirect-targets.json&lt;/code&gt;, and blocklist assets return 404.&lt;/p&gt;
&lt;p&gt;The registry is intentionally simple: one generated JSON file, schema version &lt;code&gt;2.2&lt;/code&gt;, validated before deploy, consumed by one Worker.&lt;/p&gt;
&lt;h2 id="build-inputs"&gt;Build inputs&lt;/h2&gt;
&lt;p&gt;The registry is generated from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;defaults/v8s-links.txt&lt;/code&gt;, then &lt;code&gt;custom/v8s-links.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;defaults/v8s-schedules.json&lt;/code&gt;, then &lt;code&gt;custom/v8s-schedules.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;defaults/v8s-blocklist.json&lt;/code&gt;, generated blocklist data, and &lt;code&gt;custom/v8s-blocklist.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;static page assets from &lt;code&gt;defaults/public/&lt;/code&gt; overlaid by &lt;code&gt;custom/public/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use &lt;code&gt;custom/&lt;/code&gt; for every instance-owned change. That keeps future updates simple: upstream can refresh &lt;code&gt;defaults/&lt;/code&gt; and &lt;code&gt;scripts/&lt;/code&gt; while the instance keeps its own links, policy, branding, legal pages, and generated secrets.&lt;/p&gt;</description></item><item><title>Runtime security approach</title><link>https://www.vanityurls.link/en/docs/runtime-security/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/runtime-security/</guid><description>&lt;p&gt;The vanityURLs runtime is deliberately simple. It is not a public link-submission service, not a database-backed application, and not a general web framework. It is a Git-built redirect engine: validate the link registry, deploy static assets, read &lt;code&gt;v8s.json&lt;/code&gt;, and return one of a small set of outcomes.&lt;/p&gt;
&lt;p&gt;Simplicity is part of the security model. The Worker has fewer moving parts than a typical shortener: no public write API, no visitor accounts, no cookies, no client-side analytics, no database query layer, and no origin server behind Cloudflare.&lt;/p&gt;</description></item><item><title>Scheduled links</title><link>https://www.vanityurls.link/en/docs/schedules/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/schedules/</guid><description>&lt;p&gt;Scheduled links let a stable slug point somewhere different during configured time windows. Keep the normal link in &lt;code&gt;v8s-links.txt&lt;/code&gt;, then add rules in &lt;code&gt;v8s-schedules.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Schedules currently apply to exact links. Splat links stay path-driven.&lt;/p&gt;
&lt;p&gt;For common schedule changes, use the Node CLI instead of hand-editing JSON:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./scripts/lnk schedule add hangout https://zoom.us/j/work --label work --days mon,tue,wed,thu,fri --from 09:00 --to 17:00 --timezone America/Toronto --default https://discord.gg/personal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./scripts/lnk schedule list hangout
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The CLI writes &lt;code&gt;custom/v8s-schedules.json&lt;/code&gt; by default. Set &lt;code&gt;V8S_SCHEDULES_FILE&lt;/code&gt; or pass &lt;code&gt;--file&lt;/code&gt; to use another path.&lt;/p&gt;</description></item><item><title>Server-side analytics</title><link>https://www.vanityurls.link/en/docs/server-side-analytics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/server-side-analytics/</guid><description>&lt;p&gt;vanityURLs records analytics from the Worker, not from browser JavaScript. Redirects can be measured even when the visitor never loads an HTML page, and the public pages do not need client-side tracking scripts.&lt;/p&gt;
&lt;p&gt;Analytics is non-blocking. The Worker sends events with &lt;code&gt;ctx.waitUntil()&lt;/code&gt;, so redirect latency is not tied to Umami, Fathom, or another provider being available.&lt;/p&gt;
&lt;h2 id="providers"&gt;Providers&lt;/h2&gt;
&lt;p&gt;Set one or more providers with &lt;code&gt;ANALYTICS_PROVIDER&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ANALYTICS_PROVIDER&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;umami&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;UMAMI_ENDPOINT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://cloud.umami.is/api/send&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;UMAMI_WEBSITE_ID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;umami website id&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;UMAMI_GEO_IP_MODE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;truncated&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ANALYTICS_PROVIDER&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;fathom&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;FATHOM_SITE_ID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;fathom site id&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;FATHOM_ENDPOINT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://cdn.usefathom.com/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;During a migration, send to both:&lt;/p&gt;</description></item><item><title>Upgrading an instance</title><link>https://www.vanityurls.link/en/docs/upgrading/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/upgrading/</guid><description>&lt;p&gt;Installing a vanityURLs instance is easy. Updating one safely is the part that needs a repeatable workflow, because the instance owner should keep links, branding, policy, and Cloudflare configuration while receiving new defaults, scripts, fixes, and security hardening.&lt;/p&gt;
&lt;p&gt;The rule is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;instance-owned files live in &lt;code&gt;custom/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Cloudflare deployment settings live in &lt;code&gt;wrangler.toml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;generated output is disposable&lt;/li&gt;
&lt;li&gt;upstream product files live in &lt;code&gt;defaults/&lt;/code&gt; and &lt;code&gt;scripts/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="upgrade-command"&gt;Upgrade command&lt;/h2&gt;
&lt;p&gt;Run from a clean worktree:&lt;/p&gt;</description></item><item><title>Validation and CI</title><link>https://www.vanityurls.link/en/docs/validation-ci/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.vanityurls.link/en/docs/validation-ci/</guid><description>&lt;p&gt;Run validation before every deploy. v8s is designed so link mistakes, unsafe targets, stale generated assets, and Worker regressions fail before Cloudflare receives a new version.&lt;/p&gt;
&lt;h2 id="local-commands"&gt;Local commands&lt;/h2&gt;
&lt;p&gt;Use the combined check for normal work:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm run check
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It builds the runtime, runs lint checks, and executes the Worker tests. For targeted work, use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm run lint
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm run build
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm run smoke:analytics
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run &lt;code&gt;npm run clean&lt;/code&gt; before release or before comparing generated output. It removes build cruft so the repo only shows intentional source changes.&lt;/p&gt;</description></item></channel></rss>