1.2.0: local SEO structured data (LocalBusiness, Service, FAQPage), noindex per page
This commit is contained in:
+42
-22
@@ -1,7 +1,7 @@
|
||||
import { getPayload } from 'payload';
|
||||
import { cache } from 'react';
|
||||
import { getArchiveEntries, resolveRoute as resolveRouteRaw } from '../content/index.js';
|
||||
import { buildRobots, buildSitemapEntries } from '../seo/index.js';
|
||||
import { getPayload } from 'payload';
|
||||
import { resolveRoute as resolveRouteRaw, getArchiveEntries } from '../content/index.js';
|
||||
import { buildSitemapEntries, buildRobots } from '../seo/index.js';
|
||||
/**
|
||||
* Bundles the per-request data helpers a frontend needs — the same cached
|
||||
* wrappers every project was writing by hand (getPayload, settings, locale
|
||||
@@ -27,7 +27,7 @@ import { buildRobots, buildSitemapEntries } from '../seo/index.js';
|
||||
* `resolveRoute` and `getEntries` are separate on purpose: routing decides what
|
||||
* a URL is, fetching gets the listing. Metadata generation needs the first and
|
||||
* not the second, and a page component composes them in two obvious lines.
|
||||
*/ export function createContentHelpers({ baseUrl, config, content, i18n, pagesSlug = 'pages', settingsSlug = 'site-settings' }) {
|
||||
*/ export function createContentHelpers({ config, content, settingsSlug = 'site-settings', pagesSlug = 'pages', i18n, baseUrl }) {
|
||||
const origin = baseUrl ?? process.env.NEXT_PUBLIC_SERVER_URL ?? '';
|
||||
const getCachedPayload = cache(async ()=>getPayload({
|
||||
config: await config
|
||||
@@ -40,29 +40,29 @@ import { buildRobots, buildSitemapEntries } from '../seo/index.js';
|
||||
const payload = await getCachedPayload();
|
||||
return payload.findGlobal({
|
||||
slug: settingsSlug,
|
||||
depth: 2,
|
||||
locale: locale
|
||||
locale: locale,
|
||||
depth: 2
|
||||
});
|
||||
});
|
||||
/** What does this URL point at? Routing only — no listing data. */ const resolveRoute = cache(async (locale, segments, page)=>{
|
||||
const payload = await getCachedPayload();
|
||||
return resolveRouteRaw({
|
||||
content,
|
||||
locale,
|
||||
page,
|
||||
pagesSlug,
|
||||
payload,
|
||||
locale,
|
||||
segments,
|
||||
page,
|
||||
content,
|
||||
pagesSlug,
|
||||
settingsSlug
|
||||
});
|
||||
});
|
||||
/** One page of a collection's entries, for an archive listing. */ const getEntries = cache(async (collection, locale, page, perPage)=>{
|
||||
const payload = await getCachedPayload();
|
||||
return getArchiveEntries({
|
||||
payload,
|
||||
collection,
|
||||
locale,
|
||||
page,
|
||||
payload,
|
||||
perPage
|
||||
});
|
||||
});
|
||||
@@ -73,19 +73,39 @@ import { buildRobots, buildSitemapEntries } from '../seo/index.js';
|
||||
* ```ts
|
||||
* // app/sitemap.ts
|
||||
* export { sitemap as default } from '@/lib/content'
|
||||
* export const dynamic = 'force-dynamic' // generate at runtime, not build
|
||||
* ```
|
||||
*
|
||||
* IMPORTANT — container deploys (Coolify/Docker/Railway/CI): Next treats
|
||||
* sitemap.ts as STATIC by default and prerenders it during `next build`, which
|
||||
* calls into Payload → the database. The build container usually has no access
|
||||
* to the internal Docker network, so the DB connection fails (ENOTFOUND) and
|
||||
* the build dies. Two defenses:
|
||||
* 1. `export const dynamic = 'force-dynamic'` in app/sitemap.ts — skips build
|
||||
* prerender, generates at runtime when the DB is reachable (recommended).
|
||||
* 2. This handler also catches DB errors and returns [] so that even without
|
||||
* (1) the build won't crash — it just ships an empty sitemap until the
|
||||
* next runtime regeneration. (1) is still preferred; (2) is a safety net.
|
||||
*/ const sitemap = cache(async ()=>{
|
||||
if (!i18n) {
|
||||
throw new Error('[ipal] createContentHelpers: pass `i18n` to use the sitemap handler.');
|
||||
}
|
||||
return buildSitemapEntries({
|
||||
baseUrl: origin,
|
||||
config: i18n,
|
||||
content,
|
||||
pagesSlug,
|
||||
payload: await getCachedPayload(),
|
||||
settingsSlug
|
||||
});
|
||||
try {
|
||||
return await buildSitemapEntries({
|
||||
payload: await getCachedPayload(),
|
||||
config: i18n,
|
||||
baseUrl: origin,
|
||||
content,
|
||||
pagesSlug,
|
||||
settingsSlug
|
||||
});
|
||||
} catch (error) {
|
||||
// DB unreachable (typically a container build with no DB network) — return
|
||||
// an empty sitemap instead of failing the build. Runtime regeneration will
|
||||
// produce the real one once the DB is reachable. See dynamic='force-dynamic'.
|
||||
console.warn('[ipal] sitemap: could not reach the database, returning empty entries ' + "(add `export const dynamic = 'force-dynamic'` to app/sitemap.ts to " + 'generate at runtime and avoid build-time DB access):', error);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Ready-made handler for Next's `app/robots.ts`. Re-export directly:
|
||||
@@ -100,11 +120,11 @@ import { buildRobots, buildSitemapEntries } from '../seo/index.js';
|
||||
return {
|
||||
getCachedPayload,
|
||||
getConfiguredLocales,
|
||||
getEntries,
|
||||
getSettings,
|
||||
resolveRoute,
|
||||
robots,
|
||||
sitemap
|
||||
getEntries,
|
||||
sitemap,
|
||||
robots
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user