@scttlnd/linkmask
v1.0.1
Published
Configurable same-origin affiliate links with HTTP 302 redirects.
Downloads
190
Readme
LinkMask
Show a friendly, same-origin link such as /link/garden-stones/shop in a button's hover preview. Clicking it sends a real HTTP 302 redirect to the configured affiliate URL, preserving every tracking parameter.
No dependencies, database, build step or admin account. Node.js 22 or newer is required for the CLI; the core uses standard Web Request and Response APIs.
Run it
git clone https://github.com/scottlinddk/LinkMask.git
cd LinkMask
npm ci
cp links.example.json links.json
# Replace the example URLs with your approved affiliate links.
npm start -- --config links.json --check
npm start -- --config links.json --port 3000The server defaults to 127.0.0.1. Use --host 0.0.0.0 inside a container or behind your host's TLS reverse proxy. Configuration is loaded and validated at startup; restart after editing it. links.json is ignored by Git.
{
"garden-stones/shop": "https://shop.example/products/garden-stones?paid=12345&pacid=example&utm_source=partnerads&utm_medium=affiliate&utm_campaign=12345"
}Put a normal anchor in your site (keep your visible affiliate disclosure):
<a href="/link/garden-stones/shop" rel="sponsored nofollow noopener noreferrer">
Hent tilbud
</a>Host LinkMask on the same origin as the site, or proxy /link/ to it. Running it on another domain does not mask links under your website's domain. For example, an nginx server serving the website can forward the unchanged request path:
location /link/ {
proxy_pass http://127.0.0.1:3000;
}Check a response without following the affiliate link or generating a tracked visit:
curl -I http://127.0.0.1:3000/link/garden-stones/shopExpect 302, the configured Location, and Cache-Control: no-store. Both /link/slug and /link/slug/ work. Unknown paths return 404; methods other than GET/HEAD on a known link return 405.
Use as a package
Releases are distributed on npm as @scttlnd/linkmask. Once the first release has been published:
npm install @scttlnd/linkmaskThe CLI is also available through npm exec:
npm exec --package=@scttlnd/linkmask -- linkmask --config links.json --port 3000Until the first npm publication, or when testing local changes, install a packed archive instead:
# In the LinkMask checkout:
npm pack
# In your website checkout (adjust the archive path):
npm install ../LinkMask/scttlnd-linkmask-1.0.1.tgz// Server code only: never bundle the destination registry into the browser.
import { createLinkMask } from '@scttlnd/linkmask';
const mask = createLinkMask({
'garden-stones/shop': 'https://shop.example/product?paid=12345&pacid=example',
});
// Works in a Web Request/Response route handler.
export const GET = (request) => mask.handle(request);
export const HEAD = GET;Browser components can import the path helper without importing any destinations:
import { linkPath } from '@scttlnd/linkmask/path';
linkPath('garden-stones/shop'); // /link/garden-stones/shop
linkPath('garden-stones/shop', '/my-site/link'); // /my-site/link/garden-stones/shopcreateLinkMask(links, { prefix: '/my-site/link' }) uses the same optional prefix on the server. The Node adapter is available as createLinkMaskServer from @scttlnd/linkmask/node.
Maintainers: publishing releases with GitHub Actions explains the initial npm setup, manual dry runs and versioned releases.
Affiliate template integration
The Next.js integration guide includes ready-to-copy server routes and a consent-aware link component for affiliatetemplate, with exact changes for product offers, deals, editorial CTAs and banners. It supports deployment subdirectories and includes HTTP and browser checks.
Use this integration on a Next.js server deployment. The template's static GitHub Pages showcase needs a separate redirect-capable host or same-origin proxy; copying a dynamic route into a static export cannot provide HTTP 302 redirects.
Configuration and behavior
- Slugs are case-sensitive and contain ASCII letters, numbers, hyphens and underscores, with
/between segments. Each segment starts with a letter or number. Use readable ASCII slugs for non-English product names. - Destinations must be absolute HTTP or HTTPS URLs without credentials, spaces, control characters or backslashes. Prefer HTTPS; percent-encode non-ASCII URL characters before configuration.
- Only configured destinations can be selected. Incoming query parameters are ignored, including
url,next, tracking overrides and campaign IDs. LinkMask never fetches a destination itself. - Query order, duplicate parameters, percent escapes and fragments are preserved exactly in the
Locationheader. Configuration errors identify the slug without printing its destination. - Responses disable caching and request search-engine exclusion. Redirect paths should not be added to a sitemap.
- Masking changes the displayed link before a click. The destination remains visible in HTTP headers and the address bar after redirecting; it is not encryption or secret storage.
- Keep affiliate disclosure and existing consent rules in your site. LinkMask does not obtain consent, create tracking IDs or verify affiliate program approval.
- Static file hosts such as GitHub Pages cannot run the redirect handler. Use a server deployment or a redirect-capable proxy/edge host for real 302 responses.
Verify
npm run check
npm pack --dry-runTests cover real HTTP requests, exact tracking preservation, configuration validation, unknown links, method handling and request-controlled redirect attempts. CI runs on Node 22 and 24.
