PPM

@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

VersionPublishedIntegrity
0.6.2 latest2026-10-01sha512-/ODbYHDozCLqBa+…
0.6.1 2026-10-01sha512-SLIB90Cnxn0XWDM…
0.6.0 2026-10-01sha512-4868lfRds1GS6IR…
0.5.6 2026-10-01sha512-l2pxtoWQ1ad33lk…
0.5.5 2026-10-01sha512-24qAqYpGKlSITyA…
0.5.4 2026-10-01sha512-Hn3C8ro7V5bQMf6…
0.5.3 2026-10-01sha512-xP2pNimEeHCKRtb…
0.5.2 2026-10-01sha512-CGtMkkE0dbyK5ki…
0.5.1 2026-09-30sha512-SJjCdhLfQ/P7utW…
0.5.0 2026-09-30sha512-9S/VRAOs+RypLbd…
0.4.1 2026-09-16sha512-+znh++w9YqDBrfQ…
0.4.0 2026-09-15sha512-IS+M+IGcc/XwCGf…
0.3.1 2026-09-04sha512-b/pmz29/zO7dnEp…
0.3.0 2026-09-04sha512-gpj3p4nSr4vITGZ…
0.2.2 2026-09-03sha512-Cg7perXn2zd6IGz…
0.2.1 2026-09-03sha512-D6kv+um9Tru9tk2…
0.2.0 2026-09-03sha512-8lTdfBSUo7twbjL…
0.1.0 2026-09-03sha512-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.

All packages · packument JSON