Frontend Runtime Scripts Runbook
April 23, 2026 · View on GitHub
Scope
This runbook documents the shared frontend runtime scripts that were consolidated for this repository. It focuses on how pages should wire analytics, UI widgets, and translation without reintroducing duplicated inline snippets.
Codepaths Covered
public/site-analytics.jspublic/site-widgets.jspublic/site-google-translate.jspublic/analytics-stubs.js(legacy compatibility shim)public/theme-toggle.jspublic/homelab-services-render.jspublic/homelab-services.jsonpublic/nabla-service-status.jspublic/site-content-page.css(homelab card + ping indicator styles)lib/marketingPages.tsapp/[locale]/page.tsxapp/[locale]/[slug]/page.tsxlib/htmlFromPublic.tsscripts/normalize-public-html-hrefs.mjspublic/*.htmlpages that load these scripts (for integration examples)
Runtime Architecture
The current frontend runtime is split into focused scripts:
site-analytics.js: analytics and telemetry loaders by mode.site-widgets.js: shared UI behavior (theme, translate bootstrap, scroll helpers, print button, back-to-top, coffee FAB, consent, optional Intercom).theme-toggle.js: early theme preference application and segmented theme control injection.site-google-translate.js: standalone translate bootstrap kept for compatibility with older pages.
site-widgets.js and site-google-translate.js both protect against double initialization through window.__NABLA_GOOGLE_TRANSLATE_STARTED.
Analytics Modes (site-analytics.js)
Use data-analytics-mode on the script tag:
vercel(default): Vercel analytics + Vercel speed insights.full: GTM + GA4 + VWO + PostHog + Heap + Datadog RUM + Vercel.marketing: same asfullwithout Datadog RUM.home: Mixpanel + everything infull.
Optional analytics attributes:
data-ahrefs-key: adds Ahrefs Web Analytics.data-gtm-id: override GTM container.data-ga-measurement-id: override GA4 measurement id.
Programmatic fallback is also supported through window.NABLA_ANALYTICS_PRESET.
Verified page examples
home:public/index.htmlfull:public/link.html,public/nabla.html,public/cv/index.htmlmarketing:public/truenas.html,public/freenas.htmlvercel:public/ai.html,public/contact.html,public/security.html,public/pricing.html
Shared UI Attributes (site-widgets.js)
Frequently used attributes:
data-print-pdf: inject print button.data-no-print-pdf: disable print button.data-coffee-fab: inject support floating action button.data-no-coffee-fab: disable support button.data-axeptio: load Axeptio consent SDK.data-no-google-translate: skip translate bootstrap.data-no-scroll-reveal: disable reveal animations.data-minimal-chrome: minimal UI mode (no theme toggle button, no translate, no print, no back-to-top, no Axeptio).
Default behavior to remember:
- Back-to-top is injected unless disabled (
data-no-back-to-top). - Intercom is only loaded when
data-intercom-app-idis set. - Theme preference is stored under
site-theme-preferenceand reflected viadata-themeon<html>.
Verified page examples
- Standard page with print/coffee/consent:
public/index.html,public/contact.html - Checkout pages suppressing print/coffee:
public/checkout.html,public/cancel.html - Minimal chrome mode:
public/404.html
Homelab Service Cards Runtime
public/truenas.html and public/nabla.html now render service cards from JSON at runtime instead of hardcoding each card in HTML.
Runtime flow
- Page defines one or more roots using
data-homelab-services-root. public/homelab-services-render.jsfetches JSON (homelab-services.jsonby default, ordata-homelab-jsonoverride).- Script renders a responsive card grid with Internal and Tunnel actions for each service.
- After render, script calls
window.initHomelabServiceCardPings()when available. public/nabla-service-status.jsprobes each card origin and appends status dots.
Root attributes (public interface)
data-homelab-services-root: required mount point.data-homelab-variant: optional,truenasornabla(truenasdefault). Controls default internal link tooltip text.data-homelab-json: optional JSON path, defaulthomelab-services.json.
Example:
<div
data-homelab-services-root
data-homelab-variant="truenas"
data-homelab-json="homelab-services.json"
></div>
homelab-services.json contract
Top-level payload:
{
"version": 1,
"services": []
}
Per-service fields used by renderer:
name(required): card title.iconSrc(optional): path to a local image underassets/(e.g.assets/selfh-icons/grafana.png). Validated: must start withassets/, ASCII letters/digits/._-/, no..or//. When set, the card title shows this image instead of Font Awesome. Prefer icons discovered on selfh.st/icons and vendored intopublic/assets/selfh-icons/(see.cursor/rules/506_homelab-selfh-icons.mdc).icons(optional): list of Font Awesome classes; validated against^[\\w.\\- ]+$. Invalid class falls back tofas fa-circle. Used wheniconSrcis absent or invalid.description(optional): short summary text.internalHost+internalPort(required for usable internal button): compose internal URL.internalSecure(optional boolean):httpswhen true,httpwhen false.internalPath(optional): appended path; if present and missing leading slash, renderer adds it.tunnelUrl(required for tunnel button): external URL, can behttps://...orpostgres://....newTabInternalandnewTabTunnel(optional booleans): default open in new tab unless explicitlyfalse.internalTitleandtunnelTitle(optional): override button tooltip text.portHtml(optional): custom HTML rendered in card footer (used for protocol-specific hints like Postgres).
URL-building constraints
- Internal URL falls back to
#when host/port is missing. - If
tunnelUrlstarts withpostgres:, internal URL is generated aspostgres://<host>:<port>/. - Internal scheme is selected from
internalSecure. safeHref()blocks unsafe values containing"or<(returns#).
Reachability probe behavior (nabla-service-status.js)
- Scope:
.nabla-platforms-section a.nabla-tool-tag-link#services a.opensource-link.opensource-section a.opensource-link- Homelab card action links in
.truenas-page-appsand.nabla-homelab-services
- Probe method: tries
origin + /favicon.ico, then/favicon.png, then/apple-touch-icon.png. - Concurrency: 5 origins in parallel.
- Timeout: 6500 ms per image probe.
- States:
pending: gray pulse while probing.ok: green dot when favicon load succeeds.fail: red dot on timeout/error.unknown: non-HTTP targets such aspostgres://.
Important limitations:
- Probes are best-effort hints only.
- False negatives happen when a service is up but does not expose a favicon or blocks hotlinking.
- LAN-only addresses can fail for remote users even if service is healthy internally.
Next.js Marketing Slug Bridge
lib/marketingPages.ts is the source-of-truth map for root marketing pages exposed as extensionless routes in Next.js.
Workflow:
- Add or update entry in
MARKETING_PAGES(slug,public/*.htmlfile, extract mode, body class). app/[locale]/[slug]/page.tsxgenerates static params from this map and loads the fragment vialoadPublicHtmlFragment().lib/htmlFromPublic.tsrewrites internal.htmllinks to extensionless paths for runtime navigation.scripts/normalize-public-html-hrefs.mjscan bulk-normalize legacy links inpublic/(keep itsFILE_TO_PATHmap in sync withMARKETING_PAGESand key root pages).
Practical constraints
- Keep slugs stable when possible to avoid breaking existing URLs.
- Add both mapping points when introducing a new root page:
lib/marketingPages.ts(MARKETING_PAGES)scripts/normalize-public-html-hrefs.mjs(FILE_TO_PATH)
- Use relative script paths in
public/*.html; rewrite tooling is for links, not scriptsrc.
Recommended Integration Pattern
Paths below are for public/*.html at site root. For public/cv/index.html use ../theme-toggle.js, ../site-widgets.js, etc.; for public/cv/jusmundi/*.html use ../../…. Same rule as shared CSS. Never use /site-widgets.js-style root paths for these files.
For content pages in public/ (root-level HTML):
<script src="theme-toggle.js"></script>
<script src="site-google-translate.js" defer></script>
<script src="site-analytics.js" data-analytics-mode="vercel"></script>
End of <body> (before </body>):
<script
src="site-widgets.js"
defer
data-print-pdf
data-coffee-fab
data-axeptio
></script>
For minimal utility pages:
<script src="theme-toggle.js"></script>
<script src="site-google-translate.js" defer></script>
<script src="site-analytics.js" data-analytics-mode="vercel"></script>
<!-- … -->
<script src="site-widgets.js" defer data-no-print-pdf data-no-coffee-fab></script>
For root public/404.html (minimal chrome):
<script src="site-widgets.js" defer data-minimal-chrome></script>
Troubleshooting
Theme toggle not visible:
- Confirm
theme-toggle.jsis present. - Confirm page is not using
data-minimal-chrome.
Translate widget missing:
- Check for
data-no-google-translateordata-minimal-chrome. - If both
site-widgets.jsandsite-google-translate.jsare loaded, only one init runs by design.
Print button missing:
- Confirm
data-print-pdfis present anddata-no-print-pdfis not set.
Back-to-top missing:
- Confirm
data-no-back-to-topis not set. - Confirm the page does not use
data-minimal-chrome.
Analytics mode mismatch:
- Verify the exact
data-analytics-modevalue on the page script tag. - Use page examples above to keep mode selection consistent.
Homelab cards not showing:
- Confirm page contains
data-homelab-services-root. - Confirm
homelab-services.jsonpath resolves (or setdata-homelab-jsonexplicitly). - Check browser console for
[homelab-services] Failed to load .... - Validate JSON root includes
servicesarray.
Homelab service has broken Internal URL:
- Ensure
internalHostandinternalPortare present. - Ensure
internalPathis a valid path fragment. - For Postgres services, keep
tunnelUrlscheme aspostgres://to get protocol-specific handling.
Status dots missing or stuck pending:
- Confirm
nabla-service-status.jsis loaded on the page. - Confirm
homelab-services-render.jsruns after DOM is ready and calls ping init. - If all dots are
fail, verify browser network access to target origins and favicon availability.
Migration and Compatibility Notes
public/analytics-stubs.jsis deprecated and only exists so legacy pages keep loadingsite-analytics.jsinvercelmode.- If you touch an old page, replace
analytics-stubs.jswith directsite-analytics.jsusage. - Prefer script attributes over inline third-party snippets to keep runtime behavior centralized.
public/truenas-health.jshas been removed; usepublic/nabla-service-status.jsas the shared reachability indicator script.