Skip to content
profullstackPublic

About

One footer for every Profullstack site: links, copyright, and the << Profullstack >> webring with Random. Publish once, every site updates.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

54 Commits

Folders and files

Repository files navigation

@profullstack/footer

Profullstack Distributed Footer Engine serves one versioned template to every site. Apps fetch the remote JSON template at render time, pass their own links and optional OpenIcon publisher marks, and receive sanitized HTML. Agents can use the same small options object from React, Node, or the CLI.

One footer for every Profullstack site: your links, the copyright line, and the << Profullstack >> webring nav (previous, ring, next).

Publish once, every site updates. The markup and CSS ship as data (template.json). At render time each site fetches https://cdn.jsdelivr.net/npm/@profullstack/footer@latest/template.json (15 min cache, 1.5 s timeout, the bundled copy as the fallback), so a release reaches every site within about an hour (sooner on sites without a page cache) with no bump, rebuild or redeploy. Install it as "@profullstack/footer": "latest" too, so the render code follows on the next install.

It renders on the server: the ring's verifier reads your HTML and never runs script, so a client-only footer would never count as a ring member.

Install

pnpm add @profullstack/footer@latest   # or npm i / bun add

Update the shared footer

The shared template lives in src/template.js; npm run build produces the public template.json. Sites and agents fetch that JSON with read-only requests. Only an npm publisher for @profullstack/footer can publish a version and move the latest tag, so runtime apps never need a publish key.

Next.js (App Router)

// app/layout.jsx (a server component)
import { Footer } from '@profullstack/footer/react';

<Footer site="https://example.com/" links={[{ label: 'Docs', href: '/docs' }]} since={2025} />

and the ring descriptor:

// app/.well-known/openwebring.json/route.js
import { descriptorResponse } from '@profullstack/footer';
export const GET = () => descriptorResponse({ site: 'https://example.com/', name: 'Example' });

Hono / Express / Bun / SvelteKit

import { footerHtml } from '@profullstack/footer';
const footer = await footerHtml({ site: 'https://example.com/', links: [{ label: 'Docs', href: '/docs' }] });
// Svelte: {@html footer}   Hono: c.html(`...${footer}`)

Static HTML (no server)

Print the footer once and keep it in the page (that is what the verifier sees), then let the embed swap in the @latest one in the browser:

npx @profullstack/footer html --site https://example.com/ --link Docs=/docs
<footer data-profullstack-footer data-site="https://example.com/" data-links='[{"label":"Docs","href":"/docs"}]'>
  …output of the command above…
</footer>
<script type="module" src="https://cdn.jsdelivr.net/npm/@profullstack/footer@latest/src/embed.js"></script>

Options

option default
site required your homepage; from= on every hop
links [] { label, href, rel? }[], shown before house links
since first year: © 2024–2026
owner Profullstack, Inc. → profullstack.com { name, url }
tagline after the copyright, e.g. MIT licensed
ring { slug: 'profullstack', name: 'Profullstack', host: 'https://rssamplifier.com' }
css true false drops the <style>; style .pfs-footer* yourself
houseLinks true false hides the shared Terms, Privacy, Media, GitHub, Discord, X, Events, event registration, PairUX and Moshcoding links
subscribe true false hides the newsletter form
badges [] { href, src, alt, width?, height? }[]: optional badges shown only on this site
pwamartHref https://pwamart.com/for?site=<site> Point the PwaMart icon somewhere else (the default is also the site's PwaMart ownership proof)
builtIndieHref https://built.indie.pit.world/for?site=<site> Point the built.indie icon somewhere else
nonce CSP nonce for the inline <style>; or css: false and style .pfs-footer* yourself
version latest which CDN template to render: '0.6.13' pins one (see below), 'bundled' never fetches
templateUrl full template URL; wins over version

Pinning the template version

By default every site follows @latest, which is the point of the package, but it also means a bad or compromised npm publish reaches every site within the cache window. A site that holds secrets (a secret manager, an admin app) should pin an exact version instead. jsDelivr serves an exact version immutably, so later publishes cannot change that site's footer:

When using the moving alias, an edge cache that serves a version older than the installed bundle is ignored; the bundled template stays in use until the edge catches up. Explicit version pins remain honored.

import { footerHtml } from '@profullstack/footer';
const footer = await footerHtml({ site: 'https://app.example.com/' });
<Footer site="https://app.example.com/" />

Or for a whole deployment without code changes: PROFULLSTACK_FOOTER_VERSION=0.6.13 (PROFULLSTACK_FOOTER_VERSION=bundled renders the copy in the install and never fetches). latestTemplate({ version }) and templateUrl(version) are exported too; the CLI takes --template-version 0.6.13. Bump the pin when you want a new footer.

Theme it with --pfs-footer-muted, --pfs-footer-gap and --pfs-footer-size on .pfs-footer; colors otherwise inherit from the page.

Newsletter form

Every footer carries a small email field and a Subscribe button that posts the site's hostname to pit.world. pit.world runs the double opt-in (confirmation email, nothing kept until the click) and records which property each subscriber came from (https://pit.world/admin/subscribers). The endpoint is fixed in render.js, never in the template.

The email control is labeled “Email” and uses the example address placeholder you@example.com. Its form and input set autocomplete="off"; the input also includes ignore hints for Bitwarden, 1Password, and LastPass so it is not mistaken for a login field.

Since 0.4.0 a small inline script inside the form (wireSubscribe) sends it as JSON to https://pit.world/api/subscribe and reports in place: "Email confirmation sent to you@x. Check your inbox." or "Failed: " (bad address, domain takes no mail, rate limit, mail provider down, or the request was blocked). With no script it is a plain POST to https://pit.world/subscribe, which answers with a "check your inbox" page that links back to the site. embed.js wires the form itself.

CSP: pass the page's nonce so the script runs, and allow pit.world: connect-src 'self' https://pit.world; form-action 'self' https://pit.world. If the request is blocked anyway, the form says "Failed: could not reach the subscribe service" instead of doing nothing. Turn it off with subscribe: false (--no-subscribe). Installs before 0.3.0 cannot render the form; their footer shows a plain "Newsletter" link to the same page instead, from the @latest template.

CLI

profullstack-footer html --site https://example.com/ [--link Docs=/docs] [--since 2024] [--offline] [--no-css]
profullstack-footer json --site https://example.com/          # the four ring URLs
profullstack-footer descriptor --site https://example.com/    # /.well-known/openwebring.json
profullstack-footer template                                  # the @latest template
profullstack-footer check https://example.com/                # does the live page link the ring?

Shared contact and links

Every site gets a CrawlProof Surveys link, plus links to Profullstack's Terms, Privacy and Media pages, GitHub, Discord and X, the Events calendar on Luma, a Luma event registration link, and the PairUX and Moshcoding profiles. The copyright line includes the small Profullstack icon. Every site shows the built.indie and PwaMart icons, linked to its own listing (your CSP img-src must allow https://built.indie.pit.world and https://pwamart.com). Publisher and app pages can point them elsewhere:

<Footer
  site="https://example.com/"
  pwamartHref="https://pwamart.com/publishers/example"
  builtIndieHref="https://built.indie.pit.world/p/example"
/>

The shared footer keeps the site's navigation, house links, newsletter, webring and copyright. Its compact layout leaves publisher marks on the pages that opt in. The Profullstack homepage uses the two service icons for its publisher profiles. The global phone line remains a click-to-call link.

Changing the footer for every site

Edit src/template.js, bump the version in it and in package.json, npm run build, open a PR, merge, and publish a GitHub release vX.Y.Z. The publish workflow puts it on npm and purges jsDelivr's @latest so sites pick it up on their next cache refresh. Bump renderer (and RENDERER in render.js) only when the template needs a placeholder older installs cannot fill; they keep their bundled template until reinstalled.

Safety: since 0.5.0 the filled template goes through an allowlist sanitizer (sanitizeHtml), not a regex blacklist. It tokenizes the HTML the way a browser does and rebuilds it: only footer div nav section aside header p span small strong em b i ul ol li a img br hr h2-h6 survive; attributes are limited to class title role lang dir aria-* data-pfs-* plus href rel target hreflang on a and src alt width height loading decoding on img; every value is entity-decoded, checked and re-escaped, so what was checked is what the browser sees; href accepts http:, https:, mailto:, tel: or relative URLs; src accepts http:, https:, mailto: or relative URLs, after decoding and removing control characters. Comments, unknown tags (text kept, escaped), and script/style/iframe/ svg/math/noscript/template/... with their contents are dropped. The only <style> and <form> in the output are the ones render.js builds. CSS (sanitizeCss) loses markup, escapes, comments, @import, expression(, behavior:, -moz-binding and any url() that is not https or relative. Option values are always escaped.

Env: PROFULLSTACK_FOOTER_OFFLINE=1 never fetches; PROFULLSTACK_FOOTER_VERSION pins the template version; PROFULLSTACK_FOOTER_TEMPLATE_URL overrides the CDN URL.

MIT © Profullstack, Inc.

About

One footer for every Profullstack site: links, copyright, and the << Profullstack >> webring with Random. Publish once, every site updates.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages