Build step
Add one command after your framework's build. It reads every .html file in the output folder, sends it to Headlark with your secret key and rewrites the <head> in place, before your host uploads it.
npm i -D headlark
// package.json
"scripts": {
"build": "vite build && headlark dist --site-url https://example.com"
}
Set HEADLARK_KEY to your site's secret key in the build environment (Vercel: Project → Settings → Environment Variables; Netlify: Site configuration → Environment variables; GitHub Actions: a repository secret).
Step-by-step setups: Astro, Next.js static export, Vercel, GitHub Pages.
Options
<output dir>: Vite and Astro build todist, Next.js withoutput: "export"toout.--site-url: your production address. Page URLs and canonicals are built from it (dist/about.htmlbecomeshttps://example.com/about,dist/blog/index.htmlbecomeshttps://example.com/blog/). If the site lives under a path (a GitHub Pages project site, Vitebase, Next.jsbasePath), include it: with--site-url https://user.github.io/repo/,dist/about.htmlbecomeshttps://user.github.io/repo/about.--strict: fail the build if a page can't be optimized. Without it, nothing Headlark does fails your build: a page that can't be optimized ships exactly as your framework built it, and the log says why.
Safe by default
- No
HEADLARK_KEY(a pull request from a fork, a preview without the variable, a local build): Headlark prints a line and the build continues unchanged. - A key the API doesn't know, a
--site-urlthat isn't one of your site's domains, or a CLI too old for the API: Headlark stops at the first page, says what to fix, and leaves every page as built. - Files that aren't pages are never touched: error pages (
404.html,500.html), redirect pages (a meta refresh, like Astro's redirects), Search Console verification files (google….html), files that aren't UTF-8, and files over 2,000,000 characters. - Your canonical URL, robots directive and structured data types are kept. A page marked
noindexgets no canonical (the two signals conflict). An empty result never replaces one of your tags. - Needs Node.js 20 or newer.
Good to know
- Rebuilds are free when a page's content hasn't changed. Build timestamps, counters and other incidental changes don't count as content.
- Running it twice on the same output changes nothing.
- Link previews still show the old text? Facebook and LinkedIn cache a page's preview for days or weeks. Ask them to read it again: Facebook Sharing Debugger → Scrape Again, LinkedIn Post Inspector.
- Preview images: Facebook, X and LinkedIn need an absolute JPG, PNG, WebP or GIF (1200×630 works everywhere). Headlark never uses an SVG or a
data:URL as the preview image, so a page whose only image is an SVG logo gets a small card. Set your ownog:imageto choose the picture, or set a default share image for every page that has none. Changing the default needs no new SEO: the next build adds it to unchanged pages from cache. - With review mode on, the build applies the last SEO you approved and logs
new SEO waiting for reviewfor pages with a new result. Approve it in the dashboard and the next build applies it. - Pages rendered only on the server at request time (SSR) have no HTML at build time. Prerender them, or use the deploy hook with the snippet.
- The package is on npm as
headlark.