@justanarthur/payload-www
v2.7.0
Published
Reusable Payload CMS website template — config builder, collections, globals, blocks, fields, access, hooks, metadata (JSON-LD, hreflang), page renderers, and test helpers.
Readme
@justanarthur/payload-www
A reusable Payload CMS website template: collections, globals, blocks, fields, access, JSON-LD / hreflang metadata, Next.js page renderers, sitemap, and the default plugin set (SEO + imagehash + translator + MCP).
The lib reads your next-intl routing config so locale validation, URL shape, hreflang alternates, and the language switcher share a single source of truth with the rest of the app.
The composition root is createWWWConfig() — see Quick start below.
What's inside
| piece | exported from | purpose |
|---|---|---|
| Composer | /config | createWWWConfig() — returns { withWWWConfig }. One call wires Pages + Posts + Header + Footer + the default plugin set. |
| Pages + Posts collections | createPagesCollection, createPostsCollection (internal — composed by the composer) | Pages (title, blocks tab, slug, drafts); Posts (title, excerpt, richText, drafts). Caching comes from the host's revalidatePlugin() registration. |
| Header + Footer globals | createHeaderGlobal, createFooterGlobal (internal) | Both nav blocks with navColumn / navItem. Extend via link({ extraFields }). |
| Static-page collection | createWWWCollectionGlobal (/collections) | Generic factory for system pages (404, 500, search-empty) — keyed by a discriminator, no slug. |
| Default plugins | composed by createWWWConfig() | seoPlugin, imageHashPlugin, translator, mcpPlugin. Tune via defaultPluginsConfigs. |
| Fields | /fields | link, linkGroup (with disableLabel / appearances / localized / relationTo / extraFields), slugField, appearanceOptions. |
| Access | /access | anyone, authenticated, authenticatedOrPublished. |
| Caching | Caching | Cached query getters wrap @pro-laico/payload-revalidate finders; tag-based invalidation via revalidatePlugin(); seedPayloadCache singleton seed. |
| Metadata | /metadata | buildArticleLd, buildBreadcrumbsLd, buildOrganizationLd, buildWebSiteLd, buildProductLd, buildRootJsonLd; slug transforms; queryDocBySlug, queryDocByID, queryGlobal, queryDocs, queryAllDocs, queryAllLocaleSlugs; seedPayloadCache, tagsFor. |
| Next.js page renderers | /render-pages | createCollectionPageExports, createRootLayoutExports; default render components PagesPage, PostsPage, HeaderPage, FooterPage, RootJsonLd. |
| Sitemap | /sitemap | createSitemapFromCollections (Next.js file-convention helper). |
| Plugin re-exports | /imagehash, /translator | Drop-in for hosts that don't want to import the sibling packages directly. |
Quick start
// payload.config.ts
import { buildConfig } from 'payload'
import { createWWWConfig } from '@justanarthur/payload-www/config'
import { blocks } from '@/components/blocks'
import { plugins } from '@/plugins'
const { withWWWConfig } = createWWWConfig()
export default buildConfig(withWWWConfig({
blocks, // Page block set
plugins, // host plugins appended after the lib's defaults
collections: (defaults) => [
...defaults,
Media,
Users
],
globals: (defaults) => [
...defaults.map(g => g.slug === 'header'
? { ...g, custom: { [payloadWwwName]: { path: '@/components/Header/Component#Header' } }, fields: headerFields }
: g.slug === 'footer'
? { ...g, custom: { [payloadWwwName]: { path: '@/components/Footer/Component#Footer' } }, fields: footerFields }
: g),
createStaticGlobal(),
createMessagesGlobal()
],
defaultPluginsConfigs: { // tune the lib's default plugin set
seo: (d) => ({ ...d, collections: ['pages', 'posts'], openaiApiKey: process.env.OPENAI_API_KEY }),
imageHash: (d) => ({ ...d, algorithm: 'lqip-modern' }),
translator: (d) => ({ ...d, autoTranslate: true, collections: ['pages', 'posts'], globals: ['header', 'footer', 'messages'] })
},
localization: { /* ... */ },
db: postgresAdapter({ /* ... */ }),
// ...rest of Payload config
}))createWWWConfig() takes no arguments. You tune everything via the WWWInputConfig passed to
withWWWConfig. The composer reads its own package name from package.json and uses it as the
custom.<packageName> key — that's how the block / collection / global custom.path entries get
threaded into Payload's import map.
The defaultPluginsConfigs map lets you keep the defaults (seoPlugin, imageHashPlugin, translator,
mcpPlugin) and tweak their constructor args without re-importing them. Pass (defaults) => … to
merge or … to replace.
Page exports (Next.js App Router)
// app/(frontend)/[locale]/layout.tsx
import { createRootLayoutExports } from '@justanarthur/payload-www/render-pages'
import { importMap } from '@/app/(payload)/admin/importMap'
import { routing } from '@/i18n/routing'
import config from '@payload-config'
import { getServerSideURL } from '@/lib/utils/getURL'
const { default: RootLayout, generateStaticParams } = createRootLayoutExports(
{ config, importMap, routing },
{ getServerSideURL }
)
export default RootLayout
export { generateStaticParams }// app/(frontend)/[locale]/[[...slug]]/page.tsx — Pages home + catch-all
import { createCollectionPageExports } from '@justanarthur/payload-www/render-pages'
import config from '@payload-config'
import { importMap } from '@/app/(payload)/admin/importMap'
import { routing } from '@/i18n/routing'
import { getServerSideURL } from '@/lib/utils/getURL'
const {
default: Page,
generateMetadata,
generateStaticParams,
generateSitemap
} = createCollectionPageExports(
{ config, importMap, routing, slugShape: 'catch-all' },
{ getServerSideURL }
)
export default Page
export { generateMetadata, generateStaticParams, generateSitemap }
// the doc lookup blocks so unknown slugs can return a real 404
export const instant = falseThe page looks up the document before it renders anything, so a missing slug returns HTTP 404
rather than a streamed not-found body with status 200. Slugs from generateStaticParams
prerender. Next renders any other slug on its first request, and blocks until the lookup is
done. export const instant = false tells Next's dev-time instant validation that the route
blocks on purpose. With partialPrefetching: true, Next streams the layout shell for slugs it did
not prerender, so those slugs still get a soft 404.
prerenderLocales limits which locales' documents generateStaticParams prerenders at build;
the rest render on their first request and are cached from there. A large multilingual
collection can prerender only its main locale, e.g. prerenderLocales: ['en'] for posts, to keep
the build short. It defaults to every locale.
slugShape is 'single' (default) or 'catch-all'. Use 'catch-all' if your route segment is
[[...slug]] (Pages-style), 'single' for [slug] (Posts-style — file convention).
// app/(frontend)/[locale]/posts/[slug]/page.tsx
const { default: PostPage, generateMetadata, generateStaticParams } = createCollectionPageExports(
{ config, importMap, routing, slug: 'posts', slugShape: 'single' },
{ getServerSideURL }
)createCollectionPageExports(args, deps) — args
| arg | type | default | notes |
|---|---|---|---|
| config | Promise<SanitizedConfig> | required | the host's payload.config.ts |
| importMap | ImportMap | required | the host's app/(payload)/admin/importMap |
| routing | RoutingConfig | required | { locales, defaultLocale, localePrefix, labels? } from next-intl/routing's defineRouting |
| slug | string | 'pages' | collection slug to query |
| slugShape | 'single' \| 'catch-all' | 'single' | drives generateStaticParams + slug segmentation |
| depth | number | 2 | relationship / upload hops populated on the rendered document. 0 hands every media and relationship field to the render component as a bare id. Enumeration (generateStaticParams, generateSitemap) stays at 0 regardless. |
| prerenderLocales | string[] | every served locale | locales whose documents generateStaticParams prerenders; intersected with the served locales |
| resolveLocales | ResolveLocales | — | narrows routing to the locales this deployment serves, read at runtime. See Runtime locales. |
createCollectionPageExports(args, deps) — deps
| dep | type | notes |
|---|---|---|
| getServerSideURL | () => string | host's absolute-URL helper |
| pagePathPrefix | string \| Record<locale, string> | URL segment the collection is mounted under, used for generateSitemap and for canonical/hreflang URLs. Pass a record to localize the segment ({ en: 'posts', sk: 'prispevky' }); locales absent from it fall back to the default locale. The sitemap index route always uses the default locale's segment. |
| fallback | ReactNode | rendered while the found document streams. Defaults to nothing. The lookup itself never streams, so a missing document still returns a 404. |
What you get back
{
default: Page, // the page component
generateMetadata, // Next.js MetadataRoute hook
generateStaticParams, // Next.js static-params hook
generateSitemap // MetadataRoute.Sitemap producer for /sitemap.ts
}createRootLayoutExports(args, deps)
| arg | type | notes |
|---|---|---|
| config | Promise<SanitizedConfig> | required |
| importMap | ImportMap | required |
| routing | RoutingConfig | required |
| depth | number | relationship / upload hops populated on the header + footer globals. Defaults to 2. |
| resolveLocales | ResolveLocales | same as on createCollectionPageExports; pass the same resolver to both |
| dep | type | notes |
|---|---|---|
| getServerSideURL | () => string | if provided, the SEO plugin's RootJsonLd (Organization / WebSite JSON-LD) is auto-injected as the first child of <html> |
| providers | (args) => ReactNode | wraps {children} between the rendered Header and Footer |
| htmlAttrs | (locale) => HTMLAttributes<HTMLHtmlElement> | extra <html> attributes per locale (defaults: lang={locale}, suppressHydrationWarning) |
The layout reads header + footer globals in parallel and wraps the children in
<NextIntlClientProvider>. Locale is pulled from the [locale] route segment and validated against
routing.locales — unknown locales trigger notFound().
Render modules (the page's collection component, the header and footer globals) receive locale
and the routing the request is served with as props, next to data, config and importMap.
Runtime locales (per deployment)
One codebase deployed per client, with one database each, has to give Payload the same
localization everywhere: the superset of every locale any client serves. Each deployment then
serves a subset with its own default, stored in its database. resolveLocales narrows the static
routing to that subset at runtime:
type RuntimeLocales = { locales?: readonly string[] | null; defaultLocale?: string | null }
type ResolveLocales = (routing: RoutingConfig) => Promise<RuntimeLocales | null | undefined>// i18n/locales.ts
import { localesFromGlobal } from '@justanarthur/payload-www/render-pages'
// reads `enabledLocales` + `defaultLocale` (unlocalized fields) off the `site` global
export const resolveLocales = localesFromGlobal({ globalSlug: 'site' })// app/(frontend)/[locale]/layout.tsx
createRootLayoutExports({ config, importMap, routing, resolveLocales }, { getServerSideURL })
// app/(frontend)/[locale]/[[...slug]]/page.tsx
createCollectionPageExports({ config, importMap, routing, resolveLocales, slugShape: 'catch-all' }, { getServerSideURL })routing stays the superset; resolveLocales narrows it on every render. With it:
- a locale outside the served set 404s in the layout and the page, and gets no metadata;
generateStaticParams(layout and page) lists served locales only;prerenderLocalesis intersected with them;- hreflang alternates, canonical URLs and
generateSitemapcover served locales only, and the served default gets no prefix underlocalePrefix: 'as-needed'and is thex-default; - the sitemap enumerates documents in the served default locale.
The result is sanitized by narrowRouting(routing, runtime): locales routing doesn't know are
dropped, an unset or disabled default falls back to routing.defaultLocale when served, else to
the first served locale, and an empty result keeps the full routing, so a fresh database with no
settings saved still serves every locale. Without resolveLocales nothing changes.
The resolver runs on every render, so it must be cached. localesFromGlobal({ globalSlug,
localesField?, defaultLocaleField? }) reads through queryGlobal, so it is 'use cache'd and
tagged like any global, and revalidatePlugin() refreshes it when the global is saved. A
hand-written resolver should be a 'use cache' function with a tag its source busts.
resolveRouting(routing, resolveLocales) returns the narrowed routing for host code that needs
it too (a locale switcher, getRequestConfig).
The lib doesn't own the host's middleware: next-intl's createMiddleware(routing) still decides
which locale an unprefixed URL is served in and which prefixes it redirects away. A host whose
default differs per deployment builds the middleware from the narrowed routing (e.g.
createMiddleware(narrowRouting(routing, await readLocales()))), with a cache of its own, since
'use cache' doesn't apply there.
Links
import { linkHref } from '@justanarthur/payload-www/render-pages'
linkHref(link, { locale, routing, pagePathPrefixes: { posts: 'posts' } })linkHref turns a link() field value into an href. An internal link resolves to the referenced
document's path in locale, prefixed per routing (no prefix for the default locale under
as-needed) and nested per its slug; pagePathPrefixes mounts a collection under its URL
segment. Custom URLs come back as typed. Render modules get locale and routing as props, so a
host header passes them straight through. The default HeaderPage / FooterPage use it.
buildLocalizedPath(locale, pagePathPrefix, slug, { routing }) is the same path builder for
anything that isn't a link() value.
Sitemap
// app/(frontend)/sitemap.ts
import { createSitemapFromCollections } from '@justanarthur/payload-www/sitemap'
import config from '@payload-config'
import { getServerSideURL } from '@/lib/utils/getURL'
export default createSitemapFromCollections({
getServerSideURL,
pagePathPrefix: '' // empty for root-mounted (Pages)
// pagePathPrefix: '/posts' // use this for the Posts catch-all
}, {
getServerSideURL,
pagePathPrefix: '/posts'
})createSitemapFromCollections(...args) takes one or more deps-shaped objects (the same shape
createCollectionPageExports's deps accept) and returns a Next.js MetadataRoute.Sitemap-compatible
function. Mount one per collection under a sub-route, or a single call for the root.
The lib reads through @pro-laico/payload-revalidate's cached finders, so registering
revalidatePlugin() in payload.config.ts makes every collection / global save revalidate
its tags automatically — no lib-side hooks to wire. Edits refresh the sitemap without
manual rebuilds.
Static pages (404 / 500 / system)
There's no dedicated createStaticPageExports (yet). System pages render via a normal
createCollectionPageExports({ slug: 'static-pages', slugShape: 'single' }) mount, addressed by a
discriminator instead of a slug:
// app/(frontend)/[locale]/not-found.tsx
const { default: NotFound } = createCollectionPageExports(
{ config, importMap, routing, slug: 'static-pages', slugShape: 'single' },
{ getServerSideURL }
)
export default NotFoundThe static-pages collection is built via createWWWCollectionGlobal({...}, { slug: 'static-pages', renderPath: '@/components/StaticPage/Component#StaticPage', isDraft: false }). Editors pick a kind ('not-found', 'server-error', 'search-empty', 'offline'), populate the blocks tab with the same block set you passed to createWWWConfig, and the host's not-found / server-error route renders the row. populatePublishedAt is wired automatically; revalidation comes from the host's revalidatePlugin() registration.
Collections
import {
createWWWCollectionGlobal, // generic factory (used internally for static-pages)
queryDoc // server-side helper used by renderers
} from '@justanarthur/payload-www/collections'createWWWCollectionGlobal(fields, { slug, renderPath, isGlobalConfig?, isDraft?, useAsTitle? }):
| arg | type | notes |
|---|---|---|
| fields | Field[] | the collection's field set (the factory adds slug, publishedAt, access, the populatePublishedAt beforeChange) |
| slug | string | collection slug |
| renderPath | string | import-map path to the render component ('@/components/Foo/Component#Foo') |
| isGlobalConfig | boolean | true for globals, false (default) for collections |
| isDraft | boolean | true (default) enables Payload's drafts + autosave; false for system pages |
| useAsTitle | string | the admin title, and the field the slug is generated from (see slugField) |
| nestedSlugs | boolean | _ in the slug nests the url; on for Pages, whose route is a catch-all |
The factory wires custom[packageName] = { path: renderPath }, access (create/update/delete
require auth, read is anyone or authenticatedOrPublished depending on isDraft), and
populatePublishedAt (beforeChange). It does not install revalidation hooks — those live
in the host's revalidatePlugin() registration (see Caching below).
Caching
The lib's cached query layer (queryDocBySlug, queryGlobal, queryDocs, queryAllDocs,
queryAllLocaleSlugs, queryDocByID) wraps @pro-laico/payload-revalidate's finders in 'use cache' + cacheLife('weeks')
scopes. Setup:
Install the peer deps at the workspace root (not in the lib's
dependencies—file:links in a publishedpackage.jsonbreak downstream installs):bun add @pro-laico/core @pro-laico/payload-revalidateRegister
revalidatePlugin()last inpayload.config.ts#plugins:import { revalidatePlugin } from '@pro-laico/payload-revalidate' export default buildConfig({ // ... plugins: [...otherPlugins, revalidatePlugin()], })Turn on Next's cacheComponents pipeline in
next.config.ts:const nextConfig: NextConfig = { cacheComponents: true, /* ... */ }Use the lib's getters in renderers — they're
'use cache'-wrapped already. No need to re-wrap or hand-tag:import { queryDocBySlug, queryGlobal } from '@justanarthur/payload-www/metadata' const doc = await queryDocBySlug({ collectionSlug: 'pages', slug, locale }) const header = await queryGlobal({ globalSlug: 'header', locale })seedPayloadCache({ config })runs once per process to initialize the pro-laico cache helpers from the host's Payload config.createCollectionPageExportsandcreateRootLayoutExportscall it automatically — hosts with custom layouts call it directly:import { seedPayloadCache } from '@justanarthur/payload-www/metadata' seedPayloadCache({ config })List with
queryDocs, never a rawpayload.find— a read inside'use cache'that doesn't go through the finders carries no tags, so nothing the admin does can bust it.queryDocstags a per-collectioncontentlist plus every returned doc and what is populated into it:import { queryDocs } from '@justanarthur/payload-www/metadata' const { docs, totalPages } = await queryDocs({ collectionSlug: 'posts', locale, sort: '-publishedAt', limit: 10, depth: 1 })createWWWConfigdeclares thatcontentscope on every collection over all its top-level fields, so creating, publishing, unpublishing, deleting or really changing any doc refreshes every listing of its collection — a moved date or category reorders an archive even when that post wasn't on the page. Saves that change nothing and draft saves leave it alone. For a narrower listing, declare a scope throughdefaultPluginsConfigs.revalidateand pass it aslist.
The cached profile is cacheLife('weeks') — Next 16's built-in long-tail profile (5 m stale,
1 w revalidate, 30 d expire). No custom cacheLife config entry needed. Invalidations are
purely tag-based: revalidatePlugin() fires the same tags the finders emit, so every save /
delete / publish of a Payload doc revalidates the cached render without manual rebuilds.
Fields
import { link, linkGroup, appearanceOptions, slugField } from '@justanarthur/payload-www/fields'
// link({ extraFields: [...] }) — append host fields (description, navHover, …)
// linkGroup({ appearances: ['default', 'outline'] })
// slugField({ useAsTitle: 'title', nested: true }) — slug that follows the title until unlocked
// appearanceOptions — for selects that should match link `appearances`link({ extraFields }) is the extension point for host-specific nav-link shapes. The lib's
createHeaderGlobal / createFooterGlobal accept navColumnLinkFields / navItemLinkFields that
are forwarded into their navColumn / navItem blocks.
slugField({ useAsTitle, nested }) is a localized, unique slug. With useAsTitle it pairs the
slug with a hidden, localized slugLock checkbox and renders an admin field with a Lock / Unlock
button:
- Locked (new docs start here): the slug follows the title as you type, read-only.
- Unlocked: you type the slug yourself. Diacritics are transliterated (
Prenájom→prenajom). - Nested (
nested: true, used by Pages):/nests like_, soabout/teamsaves asabout_teamand serves/about/team. A title that spells its path with spaced slashes,Products / Rental, generatesproducts_rental; any other title replaces only the last segment, so a nested page stays under its parent.Delivery/Receivingor24/7stay one segment. Flat slugs (posts, categories) never nest. - A lock that was never stored (a new doc, a locale written for the first time) turns on only
when the slug is the one the title produces. The index page's
''and seeded slugs stay unlocked, and valid slugs are never rewritten, so existing URLs stay put.
formatSlug, formatSlugSegment, generateSlug(title, parent?), slugFromTitle and
slugParent(slug) are the same helpers, exported for migrations. Adding slugLock to an existing
collection needs a migration for the new column. Leave existing docs unlocked so their live URLs
don't move.
Access
import { anyone, authenticated, authenticatedOrPublished } from '@justanarthur/payload-www/access'anyone— always true.authenticated— true when the request has a user.authenticatedOrPublished— true when authenticated or the doc is_status: 'published'.
Metadata
import {
buildArticleLd, // Article JSON-LD
buildBreadcrumbsLd, // BreadcrumbList JSON-LD
buildOrganizationLd,
buildWebSiteLd,
buildProductLd,
buildRootJsonLd, // combined Organization + WebSite + Product (used by RootJsonLd)
queryDocBySlug, // cached collection fetch
queryDocByID, // cached collection fetch by id
queryGlobal, // cached global fetch
queryDocs, // cached, tagged listing (where / sort / limit / page)
queryAllDocs, // for generateStaticParams
queryAllLocaleSlugs, // for hreflang alternates
seedPayloadCache, // one-time seed for the pro-laico cache helpers
tagsFor, // build cache tags by hand (passthrough of @pro-laico/payload-revalidate)
paramsSlugToSlug, // turn [locale]/[[...slug]] params → stored slug
slugToParamsSlug // turn stored slug → params for generateStaticParams
} from '@justanarthur/payload-www/metadata'Blocks
import { RenderBlocks } from '@justanarthur/payload-www/blocks'
export function PageBody({ blocks }) {
return <RenderBlocks blocks={blocks} />
}RenderBlocks reads each block's custom[packageName].path from Payload and dynamically imports
the matching component from the host's importMap.
Plugin re-exports
import { imageHashPlugin } from '@justanarthur/payload-www/imagehash'
import { translator } from '@justanarthur/payload-www/translator'
import { mcpPlugin } from '@justanarthur/payload-www/mcp'
import { seoPlugin } from '@justanarthur/payload-plugin-seo' // no re-export here, import directlyUse these if you want to compose the default plugin set manually outside createWWWConfig. Full
plugin docs:
@justanarthur/payload-plugin-seo@justanarthur/payload-imagehash-plugin@justanarthur/payload-plugin-translator
Public subpath exports
The package's package.json#exports map:
| Subpath | What's there |
|---|---|
| @justanarthur/payload-www/config | createWWWConfig, WWWConfigApi, WWWInputConfig |
| @justanarthur/payload-www/render-pages | createCollectionPageExports, createRootLayoutExports, PagesPage, PostsPage, HeaderPage, FooterPage, RootJsonLd, localesFromGlobal, resolveRouting, narrowRouting, linkHref, buildLocalizedPath + types (ResolveLocales, RuntimeLocales, RoutingConfig, …) |
| @justanarthur/payload-www/pages | subset of /render-pages (no createRootLayoutExports, no PostsPage, no RootJsonLd) |
| @justanarthur/payload-www/sitemap | createSitemapFromCollections |
| @justanarthur/payload-www/blocks | RenderBlocks, RenderBlocksProps |
| @justanarthur/payload-www/collections | createWWWCollectionGlobal, queryDoc |
| @justanarthur/payload-www/fields | link, linkGroup, appearanceOptions, slugField, formatSlug, formatSlugSegment, generateSlug, slugFromTitle, slugParent, SLUG_LOCK_FIELD, LinkAppearances, LinkOptions, SlugFieldOptions |
| @justanarthur/payload-www/fields-client | SlugField (the admin component slugField mounts) |
| @justanarthur/payload-www/access | anyone, authenticated, authenticatedOrPublished |
| @justanarthur/payload-www/metadata | buildArticleLd, buildBreadcrumbsLd, buildOrganizationLd, buildWebSiteLd, buildProductLd, buildRootJsonLd, queryDocBySlug, queryDocByID, queryGlobal, queryDocs, queryAllDocs, queryAllLocaleSlugs, seedPayloadCache, tagsFor, paramsSlugToSlug, slugToParamsSlug + types |
| @justanarthur/payload-www/utils | generateImportName, getFromImportMap |
| @justanarthur/payload-www/imagehash | imageHashPlugin, BlurhashPluginOptions (re-export of @justanarthur/payload-imagehash-plugin) |
| @justanarthur/payload-www/translator | translator (re-export of @justanarthur/payload-plugin-translator) |
| @justanarthur/payload-www/mcp | mcpPlugin, MCPPluginConfig (re-export of @payloadcms/plugin-mcp) |
| @justanarthur/payload-www/import-map-provider | setImportMapProvider, getImportMap (stubs in the current build — reserved for future use) |
There is no root import (.) and no /server, /with-www-config, /globals, /hooks,
/render-utils, /render-components, /render-metadata, /components, /seed, /test,
/data-seed, /data-test, /data-collections in the published exports — paths the previous
README advertised that will fail at import time.
What the previous README got wrong
For agents migrating from older docs:
createWWWConfig({ locales, blocks })→ nowcreateWWWConfig()(no args). Locales come from your Payloadlocalizationconfig;blockscome from theWWWInputConfig.blocksfield.defaultPluginscallback → nowdefaultPluginsConfigs: { seo, imageHash, translator, mcp }map onWWWInputConfig.createRevalidateCollectionHook({ collectionSlug, urlPathPrefix, … })(older CHANGELOG) andcreateRevalidateCollectionGlobalHook()(no args, 1.0.0) → both removed. Caching now goes through@pro-laico/payload-revalidate: install it as a peer dep, registerrevalidatePlugin()last inpayload.config.ts#plugins, and the lib's cached query getters (queryDocBySlug,queryGlobal,queryAllDocs,queryAllLocaleSlugs,queryDocByID) wrap its finders so the revalidation tags line up automatically. See Caching above.@justanarthur/payload-www/cache-keyssubpath (older[Unreleased]) → removed. There is no in-tree tag vocabulary to export — tags come from@pro-laico/payload-revalidate's finders.defaultPluginsConfigs.revalidateslot (older[Unreleased]) → removed. The in-lib revalidation composer is gone; revalidation is the host's responsibility viarevalidatePlugin().createSitemapFile→ not implemented. The current export iscreateSitemapFromCollections, used per-collection from yourapp/(frontend)/sitemap.ts.createPreviewHandler,LocaleSwitcher,LivePreviewListener,PageShowcase,HomePage,createStaticPageExports,createStaticPagesCollection→ none of these exist as exports in the current build. System pages render through a normalcreateCollectionPageExportsmount (see Static pages above).Pages / Posts slugislocalized: true— slug lives in<collection>_locales. There is nolocalizedornestedoption onslugField; nesting is built in.
Building
bun install
bun run build # bunup + scripts/strip-createRequire.mjs
bun run typecheck # tsc --noEmit
bun run test # vitest runThe lib uses the same bunup + src/exports/* shim pattern as the sibling plugins. One shim file
per subpath under src/exports/, each re-exporting from the implementation.
Licence
MIT
