From f7d5e79d274fdd4b99a8e3b41512b511127f3c5b Mon Sep 17 00:00:00 2001 From: rasm-its Date: Thu, 6 Aug 2026 18:44:28 +0200 Subject: [PATCH] fixed resolveRoute.ts --- dist/modules/content/resolveRoute.js | 24 ++++++++++++++++++------ dist/modules/content/resolveRoute.js.map | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/dist/modules/content/resolveRoute.js b/dist/modules/content/resolveRoute.js index d2341fb..8fedc0e 100644 --- a/dist/modules/content/resolveRoute.js +++ b/dist/modules/content/resolveRoute.js @@ -28,15 +28,27 @@ import { archiveFieldName } from './types.js'; locale: locale }); // Locale root → Homepage from System Pages. + // settings.homepage is a shallow relation (depth: 1 above) — it identifies + // WHICH page is the homepage, but its nested block relations (e.g. Hero + // images) aren't populated. Re-fetch that page from the pages collection at + // the same depth as every other page, so the frontend gets one consistent + // shape. Uses pagesSlug/locale from args — no hard-coded collection or locale. if (!segments?.length) { const homepage = settings.homepage; - if (homepage && typeof homepage === 'object') { - return { - type: 'home', - doc: homepage - }; + if (!homepage || typeof homepage !== 'object') { + return null; } - return null; + const homepageId = homepage.id; + const doc = await payload.findByID({ + id: homepageId, + collection: pagesSlug, + depth: 2, + locale: locale + }); + return doc ? { + type: 'home', + doc: doc + } : null; } const [first, ...rest] = segments; // Does the first segment name an archive page? diff --git a/dist/modules/content/resolveRoute.js.map b/dist/modules/content/resolveRoute.js.map index bb6b4dc..b0a7f5b 100644 --- a/dist/modules/content/resolveRoute.js.map +++ b/dist/modules/content/resolveRoute.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/content/resolveRoute.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { ContentOption } from './types.js'\n\nimport { archiveFieldName } from './types.js'\n\ntype ArchivePage = { id: number | string; slug?: unknown }\n\nexport type ResolvedRoute =\n /** Locale root — the page assigned as Homepage in System Pages. */\n | {\n /** The archive page this entry lives under — its slug is the URL prefix. */\n archive: Record\n collection: string\n doc: Record\n type: 'entry'\n }\n /** An ordinary page. */\n | {\n collection: string\n doc: Record\n /** 1-based, from ?page=. */\n page: number\n perPage: number\n type: 'archive'\n }\n /** A collection's archive page, e.g. /pl/artykuly. */\n | { doc: Record; type: 'home' }\n /** A single entry, e.g. /pl/artykuly/moj-post. */\n | { doc: Record; type: 'page' }\n\ntype ResolveRouteArgs = {\n content?: ContentOption\n locale: string\n /** Page number from the query string (?page=2). Defaults to 1. */\n page?: number\n /** Client's Pages collection slug. Defaults to 'pages'. */\n pagesSlug?: string\n payload: BasePayload\n /** Route segments after the locale, e.g. ['artykuly', 'moj-post']. */\n segments?: string[]\n /** SiteSettings global slug. Defaults to 'site-settings'. */\n settingsSlug?: string\n}\n\n/**\n * Works out what a URL points at: the home page, an ordinary page, a\n * collection's archive, or a single entry.\n *\n * Routing only. An archive result says which collection to list and on which\n * page, but doesn't fetch the entries — that's `getArchiveEntries`, called by\n * whoever actually needs them. Keeping the two apart means listing changes\n * (sorting, filtering, pagination) never touch routing rules, and metadata\n * generation doesn't pay for a query it would discard.\n *\n * The trick is that archive prefixes aren't configured anywhere — they're the\n * slug of whichever page an editor assigned as that collection's archive. So\n * /pl/artykuly and /en/articles come from one assignment, and renaming the page\n * moves the whole section.\n *\n * Resolution order matters: a first segment matching an archive's slug wins\n * over an ordinary page of the same name, because an archive *is* a page and\n * would otherwise shadow its own entries.\n *\n * Depth beyond {archive}/{entry} isn't supported — categories in the path would\n * make canonical and hreflang ambiguous (the same entry reachable under several\n * URLs).\n */\nexport async function resolveRoute({\n content,\n locale,\n page = 1,\n pagesSlug = 'pages',\n payload,\n segments,\n settingsSlug = 'site-settings',\n}: ResolveRouteArgs): Promise {\n const settings = (await payload.findGlobal({\n slug: settingsSlug,\n depth: 1,\n locale: locale as never,\n })) as Record\n\n // Locale root → Homepage from System Pages.\n if (!segments?.length) {\n const homepage = settings.homepage\n if (homepage && typeof homepage === 'object') {\n return { type: 'home', doc: homepage as Record }\n }\n return null\n }\n\n const [first, ...rest] = segments\n\n // Does the first segment name an archive page?\n for (const collection of content?.collections ?? []) {\n const archive = settings[archiveFieldName(collection.slug)]\n if (!archive || typeof archive !== 'object') {continue}\n\n const archiveDoc = archive as ArchivePage\n if (archiveDoc.slug !== first) {continue}\n\n // /pl/artykuly → the archive page itself.\n if (rest.length === 0) {\n return {\n type: 'archive',\n collection: collection.slug,\n doc: archiveDoc as Record,\n page,\n perPage: collection.perPage ?? 10,\n }\n }\n\n // /pl/artykuly/moj-post → an entry.\n if (rest.length === 1) {\n const found = await payload.find({\n collection: collection.slug as never,\n depth: 2,\n limit: 1,\n locale: locale as never,\n where: { slug: { equals: rest[0] } },\n })\n const entry = found.docs[0]\n if (!entry) {return null}\n\n return {\n type: 'entry',\n archive: archiveDoc as Record,\n collection: collection.slug,\n doc: entry as Record,\n }\n }\n\n // Deeper than {archive}/{entry}.\n return null\n }\n\n // An ordinary page. Joined, so nested slugs ('a/b') keep working.\n const found = await payload.find({\n collection: pagesSlug as never,\n depth: 2,\n limit: 1,\n locale: locale as never,\n where: { slug: { equals: segments.join('/') } },\n })\n const doc = found.docs[0]\n if (!doc) {return null}\n\n return { type: 'page', doc: doc as Record }\n}\n"],"names":["archiveFieldName","resolveRoute","content","locale","page","pagesSlug","payload","segments","settingsSlug","settings","findGlobal","slug","depth","length","homepage","type","doc","first","rest","collection","collections","archive","archiveDoc","perPage","found","find","limit","where","equals","entry","docs","join"],"mappings":"AAIA,SAASA,gBAAgB,QAAQ,aAAY;AAyC7C;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,eAAeC,aAAa,EACjCC,OAAO,EACPC,MAAM,EACNC,OAAO,CAAC,EACRC,YAAY,OAAO,EACnBC,OAAO,EACPC,QAAQ,EACRC,eAAe,eAAe,EACb;IACjB,MAAMC,WAAY,MAAMH,QAAQI,UAAU,CAAC;QACzCC,MAAMH;QACNI,OAAO;QACPT,QAAQA;IACV;IAEA,4CAA4C;IAC5C,IAAI,CAACI,UAAUM,QAAQ;QACrB,MAAMC,WAAWL,SAASK,QAAQ;QAClC,IAAIA,YAAY,OAAOA,aAAa,UAAU;YAC5C,OAAO;gBAAEC,MAAM;gBAAQC,KAAKF;YAAoC;QAClE;QACA,OAAO;IACT;IAEA,MAAM,CAACG,OAAO,GAAGC,KAAK,GAAGX;IAEzB,+CAA+C;IAC/C,KAAK,MAAMY,cAAcjB,SAASkB,eAAe,EAAE,CAAE;QACnD,MAAMC,UAAUZ,QAAQ,CAACT,iBAAiBmB,WAAWR,IAAI,EAAE;QAC3D,IAAI,CAACU,WAAW,OAAOA,YAAY,UAAU;YAAC;QAAQ;QAEtD,MAAMC,aAAaD;QACnB,IAAIC,WAAWX,IAAI,KAAKM,OAAO;YAAC;QAAQ;QAExC,0CAA0C;QAC1C,IAAIC,KAAKL,MAAM,KAAK,GAAG;YACrB,OAAO;gBACLE,MAAM;gBACNI,YAAYA,WAAWR,IAAI;gBAC3BK,KAAKM;gBACLlB;gBACAmB,SAASJ,WAAWI,OAAO,IAAI;YACjC;QACF;QAEA,oCAAoC;QACpC,IAAIL,KAAKL,MAAM,KAAK,GAAG;YACrB,MAAMW,QAAQ,MAAMlB,QAAQmB,IAAI,CAAC;gBAC/BN,YAAYA,WAAWR,IAAI;gBAC3BC,OAAO;gBACPc,OAAO;gBACPvB,QAAQA;gBACRwB,OAAO;oBAAEhB,MAAM;wBAAEiB,QAAQV,IAAI,CAAC,EAAE;oBAAC;gBAAE;YACrC;YACA,MAAMW,QAAQL,MAAMM,IAAI,CAAC,EAAE;YAC3B,IAAI,CAACD,OAAO;gBAAC,OAAO;YAAI;YAExB,OAAO;gBACLd,MAAM;gBACNM,SAASC;gBACTH,YAAYA,WAAWR,IAAI;gBAC3BK,KAAKa;YACP;QACF;QAEA,iCAAiC;QACjC,OAAO;IACT;IAEA,kEAAkE;IAClE,MAAML,QAAQ,MAAMlB,QAAQmB,IAAI,CAAC;QAC/BN,YAAYd;QACZO,OAAO;QACPc,OAAO;QACPvB,QAAQA;QACRwB,OAAO;YAAEhB,MAAM;gBAAEiB,QAAQrB,SAASwB,IAAI,CAAC;YAAK;QAAE;IAChD;IACA,MAAMf,MAAMQ,MAAMM,IAAI,CAAC,EAAE;IACzB,IAAI,CAACd,KAAK;QAAC,OAAO;IAAI;IAEtB,OAAO;QAAED,MAAM;QAAQC,KAAKA;IAA+B;AAC7D"} \ No newline at end of file +{"version":3,"sources":["../../../src/modules/content/resolveRoute.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { ContentOption } from './types.js'\n\nimport { archiveFieldName } from './types.js'\n\ntype ArchivePage = { id: number | string; slug?: unknown }\n\nexport type ResolvedRoute =\n /** Locale root — the page assigned as Homepage in System Pages. */\n | {\n /** The archive page this entry lives under — its slug is the URL prefix. */\n archive: Record\n collection: string\n doc: Record\n type: 'entry'\n }\n /** An ordinary page. */\n | {\n collection: string\n doc: Record\n /** 1-based, from ?page=. */\n page: number\n perPage: number\n type: 'archive'\n }\n /** A collection's archive page, e.g. /pl/artykuly. */\n | { doc: Record; type: 'home' }\n /** A single entry, e.g. /pl/artykuly/moj-post. */\n | { doc: Record; type: 'page' }\n\ntype ResolveRouteArgs = {\n content?: ContentOption\n locale: string\n /** Page number from the query string (?page=2). Defaults to 1. */\n page?: number\n /** Client's Pages collection slug. Defaults to 'pages'. */\n pagesSlug?: string\n payload: BasePayload\n /** Route segments after the locale, e.g. ['artykuly', 'moj-post']. */\n segments?: string[]\n /** SiteSettings global slug. Defaults to 'site-settings'. */\n settingsSlug?: string\n}\n\n/**\n * Works out what a URL points at: the home page, an ordinary page, a\n * collection's archive, or a single entry.\n *\n * Routing only. An archive result says which collection to list and on which\n * page, but doesn't fetch the entries — that's `getArchiveEntries`, called by\n * whoever actually needs them. Keeping the two apart means listing changes\n * (sorting, filtering, pagination) never touch routing rules, and metadata\n * generation doesn't pay for a query it would discard.\n *\n * The trick is that archive prefixes aren't configured anywhere — they're the\n * slug of whichever page an editor assigned as that collection's archive. So\n * /pl/artykuly and /en/articles come from one assignment, and renaming the page\n * moves the whole section.\n *\n * Resolution order matters: a first segment matching an archive's slug wins\n * over an ordinary page of the same name, because an archive *is* a page and\n * would otherwise shadow its own entries.\n *\n * Depth beyond {archive}/{entry} isn't supported — categories in the path would\n * make canonical and hreflang ambiguous (the same entry reachable under several\n * URLs).\n */\nexport async function resolveRoute({\n content,\n locale,\n page = 1,\n pagesSlug = 'pages',\n payload,\n segments,\n settingsSlug = 'site-settings',\n}: ResolveRouteArgs): Promise {\n const settings = (await payload.findGlobal({\n slug: settingsSlug,\n depth: 1,\n locale: locale as never,\n })) as Record\n\n // Locale root → Homepage from System Pages.\n // settings.homepage is a shallow relation (depth: 1 above) — it identifies\n // WHICH page is the homepage, but its nested block relations (e.g. Hero\n // images) aren't populated. Re-fetch that page from the pages collection at\n // the same depth as every other page, so the frontend gets one consistent\n // shape. Uses pagesSlug/locale from args — no hard-coded collection or locale.\n if (!segments?.length) {\n const homepage = settings.homepage\n if (!homepage || typeof homepage !== 'object') {\n return null\n }\n\n const homepageId = (homepage as { id: number | string }).id\n const doc = await payload.findByID({\n id: homepageId,\n collection: pagesSlug,\n depth: 2,\n locale: locale as never,\n })\n\n return doc ? { type: 'home', doc: doc as Record } : null\n }\n\n const [first, ...rest] = segments\n\n // Does the first segment name an archive page?\n for (const collection of content?.collections ?? []) {\n const archive = settings[archiveFieldName(collection.slug)]\n if (!archive || typeof archive !== 'object') {continue}\n\n const archiveDoc = archive as ArchivePage\n if (archiveDoc.slug !== first) {continue}\n\n // /pl/artykuly → the archive page itself.\n if (rest.length === 0) {\n return {\n type: 'archive',\n collection: collection.slug,\n doc: archiveDoc as Record,\n page,\n perPage: collection.perPage ?? 10,\n }\n }\n\n // /pl/artykuly/moj-post → an entry.\n if (rest.length === 1) {\n const found = await payload.find({\n collection: collection.slug as never,\n depth: 2,\n limit: 1,\n locale: locale as never,\n where: { slug: { equals: rest[0] } },\n })\n const entry = found.docs[0]\n if (!entry) {return null}\n\n return {\n type: 'entry',\n archive: archiveDoc as Record,\n collection: collection.slug,\n doc: entry as Record,\n }\n }\n\n // Deeper than {archive}/{entry}.\n return null\n }\n\n // An ordinary page. Joined, so nested slugs ('a/b') keep working.\n const found = await payload.find({\n collection: pagesSlug as never,\n depth: 2,\n limit: 1,\n locale: locale as never,\n where: { slug: { equals: segments.join('/') } },\n })\n const doc = found.docs[0]\n if (!doc) {return null}\n\n return { type: 'page', doc: doc as Record }\n}\n"],"names":["archiveFieldName","resolveRoute","content","locale","page","pagesSlug","payload","segments","settingsSlug","settings","findGlobal","slug","depth","length","homepage","homepageId","id","doc","findByID","collection","type","first","rest","collections","archive","archiveDoc","perPage","found","find","limit","where","equals","entry","docs","join"],"mappings":"AAIA,SAASA,gBAAgB,QAAQ,aAAY;AAyC7C;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,eAAeC,aAAa,EACjCC,OAAO,EACPC,MAAM,EACNC,OAAO,CAAC,EACRC,YAAY,OAAO,EACnBC,OAAO,EACPC,QAAQ,EACRC,eAAe,eAAe,EACb;IACjB,MAAMC,WAAY,MAAMH,QAAQI,UAAU,CAAC;QACzCC,MAAMH;QACNI,OAAO;QACPT,QAAQA;IACV;IAEA,4CAA4C;IAC5C,2EAA2E;IAC3E,wEAAwE;IACxE,4EAA4E;IAC5E,0EAA0E;IAC1E,+EAA+E;IAC/E,IAAI,CAACI,UAAUM,QAAQ;QACrB,MAAMC,WAAWL,SAASK,QAAQ;QAClC,IAAI,CAACA,YAAY,OAAOA,aAAa,UAAU;YAC7C,OAAO;QACT;QAEA,MAAMC,aAAa,AAACD,SAAqCE,EAAE;QAC3D,MAAMC,MAAM,MAAMX,QAAQY,QAAQ,CAAC;YACjCF,IAAID;YACJI,YAAYd;YACZO,OAAO;YACPT,QAAQA;QACV;QAEA,OAAOc,MAAM;YAAEG,MAAM;YAAQH,KAAKA;QAA+B,IAAI;IACvE;IAEA,MAAM,CAACI,OAAO,GAAGC,KAAK,GAAGf;IAEzB,+CAA+C;IAC/C,KAAK,MAAMY,cAAcjB,SAASqB,eAAe,EAAE,CAAE;QACnD,MAAMC,UAAUf,QAAQ,CAACT,iBAAiBmB,WAAWR,IAAI,EAAE;QAC3D,IAAI,CAACa,WAAW,OAAOA,YAAY,UAAU;YAAC;QAAQ;QAEtD,MAAMC,aAAaD;QACnB,IAAIC,WAAWd,IAAI,KAAKU,OAAO;YAAC;QAAQ;QAExC,0CAA0C;QAC1C,IAAIC,KAAKT,MAAM,KAAK,GAAG;YACrB,OAAO;gBACLO,MAAM;gBACND,YAAYA,WAAWR,IAAI;gBAC3BM,KAAKQ;gBACLrB;gBACAsB,SAASP,WAAWO,OAAO,IAAI;YACjC;QACF;QAEA,oCAAoC;QACpC,IAAIJ,KAAKT,MAAM,KAAK,GAAG;YACrB,MAAMc,QAAQ,MAAMrB,QAAQsB,IAAI,CAAC;gBAC/BT,YAAYA,WAAWR,IAAI;gBAC3BC,OAAO;gBACPiB,OAAO;gBACP1B,QAAQA;gBACR2B,OAAO;oBAAEnB,MAAM;wBAAEoB,QAAQT,IAAI,CAAC,EAAE;oBAAC;gBAAE;YACrC;YACA,MAAMU,QAAQL,MAAMM,IAAI,CAAC,EAAE;YAC3B,IAAI,CAACD,OAAO;gBAAC,OAAO;YAAI;YAExB,OAAO;gBACLZ,MAAM;gBACNI,SAASC;gBACTN,YAAYA,WAAWR,IAAI;gBAC3BM,KAAKe;YACP;QACF;QAEA,iCAAiC;QACjC,OAAO;IACT;IAEA,kEAAkE;IAClE,MAAML,QAAQ,MAAMrB,QAAQsB,IAAI,CAAC;QAC/BT,YAAYd;QACZO,OAAO;QACPiB,OAAO;QACP1B,QAAQA;QACR2B,OAAO;YAAEnB,MAAM;gBAAEoB,QAAQxB,SAAS2B,IAAI,CAAC;YAAK;QAAE;IAChD;IACA,MAAMjB,MAAMU,MAAMM,IAAI,CAAC,EAAE;IACzB,IAAI,CAAChB,KAAK;QAAC,OAAO;IAAI;IAEtB,OAAO;QAAEG,MAAM;QAAQH,KAAKA;IAA+B;AAC7D"} \ No newline at end of file