Plain HTML / static site
For static sites, landing pages, and site builders, Statable is one HTML line. No build step, no framework, no plugin. Paste the script tag into <head> and you are done.
This guide covers hand-written HTML, static site generators (Astro, Hugo, Eleventy, Jekyll, Next.js static export), and hosted builders (Webflow, Framer, Ghost, Carrd, Squarespace, Wix). Works on GitHub Pages, Netlify, Vercel, Cloudflare Pages, and plain Apache or Nginx.
Install
Hand-written HTML
Paste this inside <head> on every page you want to track:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My site</title>
<script src="https://statable.com/js/YOUR_SITE_ID/s.js" defer></script>
</head>
<body>
<!-- your content -->
</body>
</html>
Replace YOUR_SITE_ID with the numeric Site ID from Site settings → Tracking Code. If you have a shared header partial, paste it there once instead of editing every file.
s.js is the single tracker bundle (~2.5 KB gzip) — pageviews, engagement, scroll depth, outbound clicks, file downloads, and the window.statable.t() API.
Static site generators
Add the script tag to your shared layout or head include:
- Astro:
src/layouts/Layout.astro(or whatever your base layout is called) inside the<head>block. - Hugo:
layouts/_default/baseof.html, orlayouts/partials/head.htmlif you have one. - Eleventy (11ty):
_includes/layouts/base.njk(or.liquid,.hbs, whatever you use). - Jekyll:
_includes/head.html, or_layouts/default.htmlif you keep<head>inline. - Next.js static export: add the tag in
app/layout.tsxorpages/_document.tsx. See the Next.js install guide for the framework-aware version.
Astro example:
---
const siteId = import.meta.env.PUBLIC_STATABLE_ID
---
<html lang="en">
<head>
<script src={`https://statable.com/js/${siteId}/s.js`} defer></script>
</head>
<body><slot /></body>
</html>
Site builders
Every major builder has a Custom Code or Head HTML field. Paste the same one-liner there:
- Webflow: Project Settings -> Custom Code -> Head Code.
- Framer: Site Settings -> General -> Custom Code -> End of
<head>tag. - Ghost: Settings -> Code injection -> Site Header.
- Carrd: Site Settings -> Embed -> Style: Head, Embed code.
- Squarespace: Settings -> Advanced -> Code Injection -> Header.
- Wix: Settings -> Custom Code -> Add Custom Code -> Place Code in Head.
GitHub Pages
For Jekyll-based GitHub Pages sites, edit _includes/head.html (or create one) and paste the script tag inside <head>. Commit and push, the site rebuilds automatically. For non-Jekyll Pages sites, edit your index.html directly.
Why defer
defer downloads the script in parallel with HTML parsing and executes it after the document is parsed but before DOMContentLoaded. That gives Statable the DOM it needs to track engagement, scroll depth, and outbound clicks without blocking render. Do not use async here: Statable wraps history.pushState and reads data-id from its own tag, so deterministic execution after parsing matters.
Optional: speed up the first request
If first-load latency matters, add a connection hint above the script tag:
Saves 100 to 500 ms on the very first pageview by pre-establishing the TLS connection. Skip it if you have many third-party origins already, browsers cap how many preconnects help.
Tracking custom events
Add an inline script anywhere on the page, after the Statable tag:
<button id="signup">Sign up</button>
<script>
document.getElementById('signup').addEventListener('click', function () {
window.statable?.t?.('Signup Clicked', { source: 'hero' });
});
</script>
The optional chaining (?.t?.) silently no-ops if the deferred script hasn't loaded yet. If you depend on the event being recorded, fire it after DOMContentLoaded.
SPA navigation on a static site
Plain HTML sites are not SPAs. Every link is a full page load and Statable counts each one. Nothing to configure.
If you have a hand-rolled vanilla-JS router that uses history.pushState (rare on static sites, but it happens), Statable detects it automatically. The SDK wraps pushState / replaceState and listens for popstate and pageshow, so virtual pageviews fire on their own. If your router does not use the History API, call window.statable.t('pageview') after each transition.
Verify it's working
- Open Statable Realtime in your dashboard.
- Load your site in another tab or incognito window.
- The session should appear within a few seconds.
- Full checklist: Verify installation.
Common pitfalls
- Calling
window.statable.t(...)before the deferred script has loaded. Guard with optional chaining (window.statable?.t?.(...)) or fire afterDOMContentLoaded. - Pasting the tag in
<body>instead of<head>. Statable needs to register the page early to capture engagement and scroll depth. - Using
asyncinstead ofdefer.asynccan run before the DOM is ready and breaks history wrapping. Stick withdefer. - Forgetting one layout. If your site has multiple templates (post, page, archive), each must include the head partial or the script tag.
See also: Tracking script reference, Custom events, JavaScript API.
Ready to take control of your web analytics? Try Statable free for 30 days — no credit card required, full feature access, GDPR-compliant by default. Start your free trial or view a live demo.