@pixygon/site
The Pixygon website engine: one provider stack, the pearl page kit (SEO, i18n, language switcher, guides, hero, related products), the paid step (@pixygon/site/checkout), and the build engine every pearl runs as `pixygon-site` — so a pearl carries configuration, not engine code.
Install
# .npmrc @pixygon:registry=https://ppm.pixygon.io/ npm install @pixygon/site@0.6.2
Versions
| Version | Published | Integrity |
|---|---|---|
| 0.6.2 latest | 2026-10-01 | sha512-/ODbYHDozCLqBa+… |
| 0.6.1 | 2026-10-01 | sha512-SLIB90Cnxn0XWDM… |
| 0.6.0 | 2026-10-01 | sha512-4868lfRds1GS6IR… |
| 0.5.6 | 2026-10-01 | sha512-l2pxtoWQ1ad33lk… |
| 0.5.5 | 2026-10-01 | sha512-24qAqYpGKlSITyA… |
| 0.5.4 | 2026-10-01 | sha512-Hn3C8ro7V5bQMf6… |
| 0.5.3 | 2026-10-01 | sha512-xP2pNimEeHCKRtb… |
| 0.5.2 | 2026-10-01 | sha512-CGtMkkE0dbyK5ki… |
| 0.5.1 | 2026-09-30 | sha512-SJjCdhLfQ/P7utW… |
| 0.5.0 | 2026-09-30 | sha512-9S/VRAOs+RypLbd… |
| 0.4.1 | 2026-09-16 | sha512-+znh++w9YqDBrfQ… |
| 0.4.0 | 2026-09-15 | sha512-IS+M+IGcc/XwCGf… |
| 0.3.1 | 2026-09-04 | sha512-b/pmz29/zO7dnEp… |
| 0.3.0 | 2026-09-04 | sha512-gpj3p4nSr4vITGZ… |
| 0.2.2 | 2026-09-03 | sha512-Cg7perXn2zd6IGz… |
| 0.2.1 | 2026-09-03 | sha512-D6kv+um9Tru9tk2… |
| 0.2.0 | 2026-09-03 | sha512-8lTdfBSUo7twbjL… |
| 0.1.0 | 2026-09-03 | sha512-c0DbIISuhOmmF05… |
@pixygon/site
The Pixygon website scaffold. Every Pixygon Vite site mounts the same provider stack,
the same consent gate, the same SEO component and the same image discipline — from
one package instead of seven divergent copies of theme.ts, SEO.tsx and
basicAnalytics.ts.
import { foundationTheme } from '@pixygon/site';
import { PixygonSite, PixygonSEO, PixygonImage } from '@pixygon/site/react';
const theme = foundationTheme(); // or brandTheme('lonnlyst') / createSiteTheme({...})
root.render(
<PixygonSite
theme={theme}
analytics={{ projectId: '…', appName: 'Pixygon', endpoint: 'https://api.pixygon.io/v1/analytics', ga4Id: 'G-…' }}
auth={{ apiUrl: 'https://api.pixygon.io', projectId: '…' }} // omit on sites without accounts
>
<App />
</PixygonSite>
);
What <PixygonSite> mounts, outermost first: ErrorBoundary → HelmetProvider →
MUI ThemeProvider + CssBaseline → AnalyticsGate (three-state consent from
@pixygon/analytics, driven by this package's consent store) → AuthProvider →
BrowserRouter from react-router (router={false} if the app owns routing) → Suspense with
<PageLoader> → skip link, your app, <ConsentBanner>.
Consent
getConsent / subscribeConsent / acceptConsent / declineConsent / resetConsent
share one localStorage key across the estate. Undecided visitors are counted in
consent-free basic mode (page views + conversions, nothing stored on the device);
accepted starts the full SDK and GA4 (when ga4Id is set); declined stops
everything and wipes px_* storage and _ga cookies. useConsent() gives
components the live value. Register side effects with onConsentDecision.
Theme
createSiteTheme({ mode, primary, secondary, background, paper, fontFamily, headingFamily, overrides }) is the whole theme surface. brandTheme(key) reads the
design canon (@pixygon/design/tokens.mjs, frozen at build time) for a brand;
foundationTheme() is the Pixygon master brand.
Images
<PixygonImage src width height alt [priority] [widths] [sizes]> — sized, lazy
unless priority, async-decoded, and served through a srcSet of -{w}.webp
variants when src is on a Pixygon CDN host (variants are made by pearl images).
variantUrl / srcSetFor are exported for CSS backgrounds.
Stack manifest
stack.json pins the versions every site should be on. pearl doctor diffs a
site's package.json against it.
Engine guide pages (GuidePage + prebuild.mjs)
The SEO engine (PixygonAPI /v1/seo/*) writes search-gap pages per product. Any site renders them with one route:
import { GuidePage } from '@pixygon/site/react';
import { seoPages } from '@/data/seoPages.generated'; // written by the prebuild step
<Route path="/guide/:slug" element={<GuidePage project="tastarium" siteUrl="https://tastarium.com" siteName="Tastarium" ogImage="https://tastarium.com/og.png" baked={seoPages} defaultCta={{ label: 'Start your library', to: '/register' }} strings={{ chip: 'Guide', faqHeading: 'FAQ' }} />} />
and one script so the prerender and the sitemap see the pages without a network call:
"prebuild": "node node_modules/@pixygon/site/prebuild.mjs --project=tastarium --site=https://tastarium.com --out=src/data/seoPages.generated.ts"
Then set profile.extra.seo.createPages = true on the product's Board so the weekly engine creates pages there.
The pearl engine (0.5)
A pearl carries configuration; the engine lives here. Three parts:
Build — pixygon-site (bin). What every pearl used to copy into scripts/:
"build": "pixygon-site prebuild && tsc -b && vite build && pixygon-site postbuild",
"prerender": "pixygon-site prerender",
"visual-check": "pixygon-site visual-check"
prebuild = guides → related → prices → check-translations → check-prices; postbuild = sitemap → nginx.
Also publish-guides, declare-activation, og-image (run by hand). Languages,
origin and product come from src/config/site.ts; per-pearl variations go under
build in pixygon-seo.config.json: guides.lang, related.limit,
sitemap.priority, nginx.routeFiles, prerender.minHomeText,
visualCheck.{mustContain,neverOnPage}, publishGuides.{source,lang,forbidPrices},
activation.{event,target}, ogImage.{html,out}, prices.allow.
Prices — one catalogue, read, never written down (0.6.0). A pearl's products
and prices live only on its Project record. pixygon-site prices bakes
GET /v1/pricing/<slug> into src/data/prices.generated.ts (kept as committed
when the API is down; a selling pearl with nothing committed fails); pass it to
definePearl(site, { …, pricing }). site.ts PRODUCT names { key, kind }
only (priceNOK is deprecated; the generated price wins). The prerender and
crawlers show the baked price (chargeable.nok ? prices.nok : prices[base]);
a visitor sees their own from /stripe/tiers (usePearlPrice(key),
usePearlProduct()). pixygon-site check-prices fails a build whose site.ts,
locales or source hold OUR price as a literal; allow a competitor's equal figure
with a // price-literal-ok comment or build.prices.allow: [<locale key>].
The scanner is exported as @pixygon/site/price-literals.mjs for pearl doctor.
nginx / Docker. docker/nginx.base.conf is the server every pearl runs;
pixygon-site nginx fills it with the pearl's nginx.http.conf (http level),
nginx.extra.conf (inside server {}) and the route allowlist, and writes
nginx.generated.conf. The template Dockerfile copies that file and runs
npx pixygon-site prerender, which bakes, strips loopback origins and refuses to
ship a shell (or Stripe code in a free pearl). No registry image is needed: the
base travels in node_modules.
Page kit — @pixygon/site/react. Bind once in src/i18n/index.tsx:
export const { I18nProvider, useI18n, useT, localizePath, guidesFor } =
definePearl(site, { dictionaries: { no, en }, related: relatedProducts, guides: { pages: seoPages, translations: { en } } })
then use SEO, LanguageSwitcher, SiteTopBar, SiteFooter, RelatedProducts,
RichText, HeroShowcase + DemoKit, BuyButton, SupportCard, NotFoundPage,
PearlGuidePage, PearlGuideIndexPage, useScrollToTop, installSafeLocalStorage.
The engine ships its own no/en strings; a pearl's locale keys always win.
t() reads flat or nested locale files and takes the i18next call shapes:
t(k, vars), t(k, 'Default'), t(k, 'Default', vars), count plurals
(k_one/k_other, ordinal: true), returnObjects. The source language is
served at /, every other under /<lang>/ (English-first is DEFAULT_LANG: 'en').
Options for app-like pearls: stickyLanguage (an in-app push from /no to an
unprefixed path stays in /no), routerBasename (the provider owns the router
and the language is its basename; router links use to(), href() stays the
full path), keepLanguageOn: /^\/guide/ (pages the browser preference never
redirects off), hardReloadOnSwitch, globalVars. setLang(lang, to?).
Checkout — @pixygon/site/checkout (ESM only). CheckoutPage / CheckoutReturnPage
driven by PRODUCT in site.ts; the server owns the price. Import it only under
__PEARL_PRICED__ so a free pearl bundles no Stripe code.
Migrating a pearl: pearl template sync <repo> (template v4) replaces every
engine copy that matches a template version and keeps real variations with a diff.