Skip to Content

Frontend Map

The Frontend Map is the single source of truth for how each site’s content is structured into URLs. It declares, per site, the rule that maps a content type to a frontend pathname.

It lives in the shared workspace package @nelioscom/site-structure (source: src/packages/site-structure/src/index.ts) and exports the frontendMap data together with the builder/matcher helpers that consume it.


Where it is used

The same data and builders are consumed in several places, so URL logic never drifts between them:

ConsumerWhat it uses it for
config/admin.tsThe Strapi Preview button (buildPreviewPath)
src/api/frontendmap/...Sitemap generation + inbound path matching
config/plugins.tsnelios-popup plugin config (frontendMap)

Because it is a compiled workspace package, after editing the source you must rebuild it (pnpm run build:shared from the backend root, or npm run build inside the package) before consumers pick up the change.


How a rule works

Each content type has one rule string, and that single rule is used for both directions:

  • Outbound — building a URL for a document (Preview, sitemap).
  • Inbound — matching an incoming pathname back to a content type.

A rule is a /-separated template of literal segments and tokens, e.g.:

{locale}/{hotel}/accommodation/{?category}/{slug}

Keep each rule in sync with the actual frontend route structure.


Token reference

Tokens appear inside a rule. Wrapping a token as {?token} makes it optional — the segment is dropped when its value is empty.

TokenExpands to
{locale}The locale segment. Omitted when it equals the site defaultLocale.
{slug}The document’s Slug.
{hotel}prefixHotels + the document’s direct Hotel slug.
{parents}The recursive Parent-page slug chain (root → parent).
{parents_h}{parents} plus the Hotel of the root ancestor (hotelGroup sites).
{category}The document’s Category field, lowercased for the URL segment.
{?token}Optional variant of any token — dropped when the value is empty.

Data shape

The map is an array of site configs, each holding an array of rules.

export type FrontendMapRule = { collectionType: string; // content-type UID, e.g. plugin::uzenie.uzenie-page rule: string; // URL template (see tokens above) prefix?: string; // optional path prefix used for inbound matching }; export type FrontendMapSite = { site: string; // site key, e.g. "uzenie" prefixHotels: string; // auto-prefix applied when {hotel} is used (e.g. "/hotels") hotelGroup: boolean; // true for multi-hotel sites; enables {parents_h} defaultLocale: string; // locale that is omitted from the URL (e.g. "en") defaultCollectionType: string; // fallback content type for the site rules: FrontendMapRule[]; }; export const frontendMap: FrontendMapSite[] = [ /* ...one entry per site... */ ];

Site fields

FieldDescription
siteUnique site key.
prefixHotelsSegment(s) automatically inserted before a hotel slug whenever {hotel} (or {parents_h}) resolves. Empty string for single-property sites.
hotelGrouptrue for hotel-group sites. Enables {parents_h}, which appends the root ancestor’s hotel.
defaultLocaleThe locale that is not rendered as a URL segment.
defaultCollectionTypeDefault content type for the site (used as a fallback).
rulesThe per-content-type URL rules.

Example: site config

export const uzenie = { site: 'uzenie', defaultLocale: 'en', prefixHotels: '', hotelGroup: false, defaultCollectionType: 'plugin::uzenie.uzenie-page', rules: [ { collectionType: 'plugin::uzenie.uzenie-homepage', rule: '{locale}' }, { collectionType: 'plugin::uzenie.uzenie-page', rule: '{locale}/{?parents}/{slug}' }, { collectionType: 'plugin::uzenie.uzenie-room', rule: '{locale}/accommodation/{slug}', prefix: 'accommodation' }, { collectionType: 'plugin::uzenie.uzenie-offer', rule: '{locale}/offers/{slug}', prefix: 'offers' }, { collectionType: 'plugin::uzenie.uzenie-restaurant', rule: '{locale}/gastronomy/{slug}', prefix: 'gastronomy' }, { collectionType: 'plugin::uzenie.uzenie-experience', rule: '{locale}/experiences/{slug}', prefix: 'experiences' }, ], };

A hotel-group site such as andronis (hotelGroup: true, prefixHotels: '/hotels') uses the hotel tokens:

export const andronisRules = [ { collectionType: 'plugin::andronis.andronis-page', rule: '{locale}/{?hotel}/{?parents}/{slug}' }, { collectionType: 'plugin::andronis.andronis-room', rule: '{locale}/{hotel}/accommodation/{?category}/{slug}', prefix: 'accommodation' }, ];

Example resolutions

SiteRuleDocumentResult
uzenie{locale}/{?parents}/{slug}slug: "wellness", locale en (default)/wellness
uzenie{locale}/{?parents}/{slug}slug: "wellness", locale el/el/wellness
uzenie{locale}/offers/{slug}slug: "early-bird", locale en/offers/early-bird
andronis{locale}/{hotel}/accommodation/{?category}/{slug}hotel arts, category Suites, slug caldera, locale en/hotels/arts/accommodation/suites/caldera

Exported helpers

Resolution

  • findSiteRule(uid) — returns the { siteCfg, ruleEntry } that owns a content-type UID, or null.
  • buildPreviewPath({ strapi, uid, documentId, locale?, status? }) — async; resolves a single document to a frontend pathname using the rules. Returns null when no rule exists for the UID (caller falls back to its own logic) or the document can’t be found.
  • buildUrlForDoc({ doc, ruleEntry, siteCfg }) — builds the pathname from an already-fetched document.
  • buildFindManyQuery({ strapi, uid, rule, parentDepth? }) — derives the Strapi fields/populate needed to satisfy a rule’s tokens (e.g. recursively populating Parent and Hotel).
  • frontendBaseUrl(uid) — resolves a site’s base URL from its UID, e.g. plugin::andronis.andronis-pageFRONT_END_URL_ANDRONIS.

Sitemap

  • buildSitemapXml({ urls, lastmodByUrl? }) — renders a <urlset> sitemap document from a list of URLs (with optional lastmod per URL).

Path utilities

  • normalizePathname(p) — trims query/hash, collapses duplicate slashes, removes trailing slash.
  • splitSegments(p) — normalizes and splits a pathname into decoded segments.
  • extractRuleTokens(rule) — returns the unique token names inside a rule.
  • isLocaleSeg(s) — tests whether a segment looks like a locale (en, el, en-US).
  • joinPath(...parts) — joins parts into a normalized pathname.

Adding or changing a route

  1. Add or edit the relevant FrontendMapRule under the correct site in frontendMap.
  2. Make sure the rule matches the actual frontend route shape for that content type.
  3. Set prefix when the route has a fixed leading segment used for inbound matching (e.g. offers, accommodation).
  4. For hotel-group sites, use {hotel} / {parents_h} and set prefixHotels accordingly.
  5. Rebuild the package (pnpm run build:shared) so all consumers pick up the change.
Last updated on