Compare commits
2
Commits
e2a703fc72
...
4a46651839
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a46651839 | ||
|
|
e7f06548fb |
+27
-13
@@ -1,14 +1,25 @@
|
||||
import type { SitemapEntry } from './buildSitemapEntries.js';
|
||||
type BuildSitemapXmlOptions = {
|
||||
/**
|
||||
* URL of a CSS stylesheet to make the sitemap readable in the browser, e.g.
|
||||
* '/sitemap.css'. Uses `type="text/css"` — the W3C "Associating Style Sheets
|
||||
* with XML" mechanism, which is NOT deprecated (unlike XSLT / type="text/xsl",
|
||||
* which Chrome/WebKit are removing). CSS on XML shows no warning, styles the
|
||||
* raw tags directly (e.g. `url { display: block }` turns the wall of text into
|
||||
* cards), and crawlers ignore the directive entirely.
|
||||
*/
|
||||
cssUrl?: string;
|
||||
};
|
||||
/**
|
||||
* Serializes sitemap entries to a clean, indented XML STRING — valid for crawlers
|
||||
* and readable in the browser as the native XML tree (indented, collapsible,
|
||||
* syntax-highlighted by the browser itself).
|
||||
* and (with `cssUrl`) styled in the browser via plain CSS.
|
||||
*
|
||||
* NO XSLT stylesheet: browsers (Chrome et al.) are REMOVING XSLT support, so a
|
||||
* <?xml-stylesheet?> approach is a dead end — it shows a deprecation warning now
|
||||
* and will break soon. Instead we serve plain XML with the correct Content-Type;
|
||||
* the browser renders its built-in formatted XML view (the "code tree" look) with
|
||||
* no transformation needed.
|
||||
* Two viewing modes:
|
||||
* - No cssUrl → the browser's native formatted XML tree (indented, collapsible).
|
||||
* - With cssUrl → a `<?xml-stylesheet type="text/css">` directive; the project's
|
||||
* CSS styles the XML tags (cards, labels via ::before). NOT XSLT — that's being
|
||||
* removed from browsers and shows a deprecation warning. CSS is safe and
|
||||
* W3C-standard.
|
||||
*
|
||||
* // app/sitemap.xml/route.ts
|
||||
* import { buildSitemapXml } from '@intecion/ipal-kit'
|
||||
@@ -16,16 +27,19 @@ import type { SitemapEntry } from './buildSitemapEntries.js';
|
||||
* export const dynamic = 'force-dynamic'
|
||||
* export async function GET() {
|
||||
* const entries = await sitemap()
|
||||
* const xml = buildSitemapXml(entries)
|
||||
* const xml = buildSitemapXml(entries, { cssUrl: '/sitemap.css' })
|
||||
* return new Response(xml, {
|
||||
* headers: { 'Content-Type': 'application/xml; charset=utf-8' },
|
||||
* })
|
||||
* }
|
||||
*
|
||||
* The indentation makes the raw XML pleasant to read; the browser's default XML
|
||||
* viewer adds the tree/fold UI. Crawlers read the XML as usual.
|
||||
* Put sitemap.css in the project's /public and style the tags (see docs/seo.md
|
||||
* for a starter). Note: <loc> is an XML tag, not <a href> — CSS can't make it a
|
||||
* clickable link (some browsers auto-detect URLs); the win is readability, not
|
||||
* clickability.
|
||||
*
|
||||
* NOTE: if you use this custom route, DON'T also keep app/sitemap.ts — pick one
|
||||
* (this route OR the Next MetadataRoute). Two sitemaps confuse crawlers.
|
||||
* NOTE: if you use this custom route, DON'T also keep app/sitemap.ts — pick one.
|
||||
* Two sitemaps confuse crawlers.
|
||||
*/
|
||||
export declare function buildSitemapXml(entries: SitemapEntry[]): string;
|
||||
export declare function buildSitemapXml(entries: SitemapEntry[], opts?: BuildSitemapXmlOptions): string;
|
||||
export {};
|
||||
|
||||
Vendored
+20
-20
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Serializes sitemap entries to a clean, indented XML STRING — valid for crawlers
|
||||
* and readable in the browser as the native XML tree (indented, collapsible,
|
||||
* syntax-highlighted by the browser itself).
|
||||
* and (with `cssUrl`) styled in the browser via plain CSS.
|
||||
*
|
||||
* NO XSLT stylesheet: browsers (Chrome et al.) are REMOVING XSLT support, so a
|
||||
* <?xml-stylesheet?> approach is a dead end — it shows a deprecation warning now
|
||||
* and will break soon. Instead we serve plain XML with the correct Content-Type;
|
||||
* the browser renders its built-in formatted XML view (the "code tree" look) with
|
||||
* no transformation needed.
|
||||
* Two viewing modes:
|
||||
* - No cssUrl → the browser's native formatted XML tree (indented, collapsible).
|
||||
* - With cssUrl → a `<?xml-stylesheet type="text/css">` directive; the project's
|
||||
* CSS styles the XML tags (cards, labels via ::before). NOT XSLT — that's being
|
||||
* removed from browsers and shows a deprecation warning. CSS is safe and
|
||||
* W3C-standard.
|
||||
*
|
||||
* // app/sitemap.xml/route.ts
|
||||
* import { buildSitemapXml } from '@intecion/ipal-kit'
|
||||
@@ -15,18 +15,21 @@
|
||||
* export const dynamic = 'force-dynamic'
|
||||
* export async function GET() {
|
||||
* const entries = await sitemap()
|
||||
* const xml = buildSitemapXml(entries)
|
||||
* const xml = buildSitemapXml(entries, { cssUrl: '/sitemap.css' })
|
||||
* return new Response(xml, {
|
||||
* headers: { 'Content-Type': 'application/xml; charset=utf-8' },
|
||||
* })
|
||||
* }
|
||||
*
|
||||
* The indentation makes the raw XML pleasant to read; the browser's default XML
|
||||
* viewer adds the tree/fold UI. Crawlers read the XML as usual.
|
||||
* Put sitemap.css in the project's /public and style the tags (see docs/seo.md
|
||||
* for a starter). Note: <loc> is an XML tag, not <a href> — CSS can't make it a
|
||||
* clickable link (some browsers auto-detect URLs); the win is readability, not
|
||||
* clickability.
|
||||
*
|
||||
* NOTE: if you use this custom route, DON'T also keep app/sitemap.ts — pick one
|
||||
* (this route OR the Next MetadataRoute). Two sitemaps confuse crawlers.
|
||||
*/ export function buildSitemapXml(entries) {
|
||||
* NOTE: if you use this custom route, DON'T also keep app/sitemap.ts — pick one.
|
||||
* Two sitemaps confuse crawlers.
|
||||
*/ export function buildSitemapXml(entries, opts = {}) {
|
||||
const { cssUrl } = opts;
|
||||
const esc = (s)=>s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
||||
const urls = entries.map((e)=>{
|
||||
const parts = [
|
||||
@@ -36,12 +39,8 @@
|
||||
const iso = e.lastModified instanceof Date ? e.lastModified.toISOString() : String(e.lastModified);
|
||||
parts.push(` <lastmod>${esc(iso)}</lastmod>`);
|
||||
}
|
||||
if (e.changeFrequency) {
|
||||
parts.push(` <changefreq>${e.changeFrequency}</changefreq>`);
|
||||
}
|
||||
if (typeof e.priority === 'number') {
|
||||
parts.push(` <priority>${e.priority}</priority>`);
|
||||
}
|
||||
if (e.changeFrequency) parts.push(` <changefreq>${e.changeFrequency}</changefreq>`);
|
||||
if (typeof e.priority === 'number') parts.push(` <priority>${e.priority}</priority>`);
|
||||
const alternates = e.alternates?.languages;
|
||||
if (alternates) {
|
||||
for (const [lang, href] of Object.entries(alternates)){
|
||||
@@ -52,7 +51,8 @@
|
||||
}
|
||||
return ` <url>\n${parts.join('\n')}\n </url>`;
|
||||
}).join('\n');
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>\n` + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ` + `xmlns:xhtml="http://www.w3.org/1999/xhtml">\n` + urls + `\n</urlset>`;
|
||||
const stylesheet = cssUrl ? `<?xml-stylesheet type="text/css" href="${esc(cssUrl)}"?>\n` : '';
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>\n` + stylesheet + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ` + `xmlns:xhtml="http://www.w3.org/1999/xhtml">\n` + urls + `\n</urlset>`;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=buildSitemapXml.js.map
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+65
-14
@@ -350,12 +350,12 @@ const jsonLd = buildOrganizationJsonLd({
|
||||
url: process.env.NEXT_PUBLIC_SERVER_URL!,
|
||||
logo: settings.logo,
|
||||
sameAs: settings.socialLinks, // opcjonalne: profile społecznościowe
|
||||
})
|
||||
})
|
||||
|
||||
// w JSX layoutu:
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
// w JSX layoutu:
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -522,16 +522,16 @@ zanim strona wygeneruje metadane. To pcha metadata do body. NIE deklaruj `<head>
|
||||
```tsx
|
||||
// ŹLE — jawny <head> zamyka head za wcześnie
|
||||
<html lang={locale}>
|
||||
<head><MediaPreconnect /></head>
|
||||
<body>{children}</body>
|
||||
<head><MediaPreconnect /></head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
|
||||
// DOBRZE — MediaPreconnect w body, React 19 hoistuje link do head
|
||||
<html lang={locale}>
|
||||
<body>
|
||||
<MediaPreconnect />
|
||||
{children}
|
||||
</body>
|
||||
<body>
|
||||
<MediaPreconnect />
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
@@ -914,9 +914,60 @@ export async function GET() {
|
||||
}
|
||||
```
|
||||
|
||||
`buildSitemapXml` zwraca wcięty XML (czytelny w surowej postaci), a przeglądarka
|
||||
dokłada widok drzewa. Zawiera hreflang jako `<xhtml:link>`. Crawlery czytają XML
|
||||
normalnie.
|
||||
`buildSitemapXml` zwraca wcięty XML. Dwa tryby wyświetlania:
|
||||
|
||||
**Bez `cssUrl`** → przeglądarka pokazuje natywny widok drzewa XML (wcięcia, zwijanie).
|
||||
|
||||
**Z `cssUrl`** → stylujesz XML własnym CSS (kafelki, etykiety). To W3C standard
|
||||
„Associating Style Sheets with XML" — `type="text/css"`, NIE wycofywane (w
|
||||
przeciwieństwie do XSLT/`text/xsl`). Zero ostrzeżenia, ładny wygląd, bezpieczne
|
||||
dla Google (crawlery ignorują dyrektywę).
|
||||
|
||||
```ts
|
||||
const xml = buildSitemapXml(entries, { cssUrl: '/sitemap.css' })
|
||||
```
|
||||
|
||||
### Starter CSS (public/sitemap.css)
|
||||
|
||||
Skopiuj do `public/sitemap.css` w projekcie, dostosuj do designu. Selektory to
|
||||
bezpośrednio nazwy tagów XML:
|
||||
|
||||
```css
|
||||
/* public/sitemap.css */
|
||||
urlset {
|
||||
display: block;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #090d16; color: #f1f5f9;
|
||||
padding: 2rem 1.5rem; max-width: 1200px; margin: 0 auto;
|
||||
}
|
||||
url { /* każdy adres jako kafelek */
|
||||
display: block;
|
||||
background: #111827; border: 1px solid #1e293b; border-radius: 8px;
|
||||
padding: 1rem 1.25rem; margin-bottom: 0.75rem;
|
||||
}
|
||||
loc { /* adres URL */
|
||||
display: block; font-size: 0.95rem; font-weight: 600;
|
||||
color: #f97316; margin-bottom: 0.5rem; word-break: break-all;
|
||||
}
|
||||
lastmod, changefreq, priority {
|
||||
display: inline-block; font-size: 0.8rem; color: #94a3b8; margin-right: 1.5rem;
|
||||
}
|
||||
lastmod::before { content: 'Ostatnia modyfikacja: '; color: #64748b; }
|
||||
changefreq::before { content: 'Częstotliwość: '; color: #64748b; }
|
||||
priority::before { content: 'Priorytet: '; color: #64748b; }
|
||||
link { /* tagi hreflang */
|
||||
display: inline-block; font-size: 0.75rem;
|
||||
background: #1e293b; color: #38bdf8; border: 1px solid #334155;
|
||||
padding: 0.15rem 0.45rem; border-radius: 4px; margin: 0.4rem 0.35rem 0 0;
|
||||
}
|
||||
```
|
||||
|
||||
Kluczowe: `display: block` na `<url>`/`<loc>` zamienia „ścianę tekstu" w kafelki.
|
||||
Etykiety („Ostatnia modyfikacja:") przez `::before`. Dostosuj kolory do projektu.
|
||||
|
||||
**Ograniczenie:** `<loc>` to tag XML, nie `<a href>` — CSS nie zrobi z niego
|
||||
klikalnego linku (niektóre przeglądarki autodetektują URL). Zysk to czytelność
|
||||
i organizacja, nie klikalność. Dla sitemap (głównie dla robotów) to akceptowalne.
|
||||
|
||||
### WAŻNE — jeden sitemap, nie dwa
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@intecion/ipal-kit",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"description": "Intecion Payload Advanced Library — a Payload CMS 3 plugin: i18n, SEO, forms, consent, analytics, blog/archives.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
import type { SitemapEntry } from './buildSitemapEntries.js'
|
||||
|
||||
type BuildSitemapXmlOptions = {
|
||||
/**
|
||||
* URL of a CSS stylesheet to make the sitemap readable in the browser, e.g.
|
||||
* '/sitemap.css'. Uses `type="text/css"` — the W3C "Associating Style Sheets
|
||||
* with XML" mechanism, which is NOT deprecated (unlike XSLT / type="text/xsl",
|
||||
* which Chrome/WebKit are removing). CSS on XML shows no warning, styles the
|
||||
* raw tags directly (e.g. `url { display: block }` turns the wall of text into
|
||||
* cards), and crawlers ignore the directive entirely.
|
||||
*/
|
||||
cssUrl?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes sitemap entries to a clean, indented XML STRING — valid for crawlers
|
||||
* and readable in the browser as the native XML tree (indented, collapsible,
|
||||
* syntax-highlighted by the browser itself).
|
||||
* and (with `cssUrl`) styled in the browser via plain CSS.
|
||||
*
|
||||
* NO XSLT stylesheet: browsers (Chrome et al.) are REMOVING XSLT support, so a
|
||||
* <?xml-stylesheet?> approach is a dead end — it shows a deprecation warning now
|
||||
* and will break soon. Instead we serve plain XML with the correct Content-Type;
|
||||
* the browser renders its built-in formatted XML view (the "code tree" look) with
|
||||
* no transformation needed.
|
||||
* Two viewing modes:
|
||||
* - No cssUrl → the browser's native formatted XML tree (indented, collapsible).
|
||||
* - With cssUrl → a `<?xml-stylesheet type="text/css">` directive; the project's
|
||||
* CSS styles the XML tags (cards, labels via ::before). NOT XSLT — that's being
|
||||
* removed from browsers and shows a deprecation warning. CSS is safe and
|
||||
* W3C-standard.
|
||||
*
|
||||
* // app/sitemap.xml/route.ts
|
||||
* import { buildSitemapXml } from '@intecion/ipal-kit'
|
||||
@@ -17,19 +29,26 @@ import type { SitemapEntry } from './buildSitemapEntries.js'
|
||||
* export const dynamic = 'force-dynamic'
|
||||
* export async function GET() {
|
||||
* const entries = await sitemap()
|
||||
* const xml = buildSitemapXml(entries)
|
||||
* const xml = buildSitemapXml(entries, { cssUrl: '/sitemap.css' })
|
||||
* return new Response(xml, {
|
||||
* headers: { 'Content-Type': 'application/xml; charset=utf-8' },
|
||||
* })
|
||||
* }
|
||||
*
|
||||
* The indentation makes the raw XML pleasant to read; the browser's default XML
|
||||
* viewer adds the tree/fold UI. Crawlers read the XML as usual.
|
||||
* Put sitemap.css in the project's /public and style the tags (see docs/seo.md
|
||||
* for a starter). Note: <loc> is an XML tag, not <a href> — CSS can't make it a
|
||||
* clickable link (some browsers auto-detect URLs); the win is readability, not
|
||||
* clickability.
|
||||
*
|
||||
* NOTE: if you use this custom route, DON'T also keep app/sitemap.ts — pick one
|
||||
* (this route OR the Next MetadataRoute). Two sitemaps confuse crawlers.
|
||||
* NOTE: if you use this custom route, DON'T also keep app/sitemap.ts — pick one.
|
||||
* Two sitemaps confuse crawlers.
|
||||
*/
|
||||
export function buildSitemapXml(entries: SitemapEntry[]): string {
|
||||
export function buildSitemapXml(
|
||||
entries: SitemapEntry[],
|
||||
opts: BuildSitemapXmlOptions = {},
|
||||
): string {
|
||||
const { cssUrl } = opts
|
||||
|
||||
const esc = (s: string): string =>
|
||||
s
|
||||
.replace(/&/g, '&')
|
||||
@@ -46,8 +65,8 @@ export function buildSitemapXml(entries: SitemapEntry[]): string {
|
||||
e.lastModified instanceof Date ? e.lastModified.toISOString() : String(e.lastModified)
|
||||
parts.push(` <lastmod>${esc(iso)}</lastmod>`)
|
||||
}
|
||||
if (e.changeFrequency) {parts.push(` <changefreq>${e.changeFrequency}</changefreq>`)}
|
||||
if (typeof e.priority === 'number') {parts.push(` <priority>${e.priority}</priority>`)}
|
||||
if (e.changeFrequency) parts.push(` <changefreq>${e.changeFrequency}</changefreq>`)
|
||||
if (typeof e.priority === 'number') parts.push(` <priority>${e.priority}</priority>`)
|
||||
const alternates = e.alternates?.languages
|
||||
if (alternates) {
|
||||
for (const [lang, href] of Object.entries(alternates)) {
|
||||
@@ -62,8 +81,11 @@ export function buildSitemapXml(entries: SitemapEntry[]): string {
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
const stylesheet = cssUrl ? `<?xml-stylesheet type="text/css" href="${esc(cssUrl)}"?>\n` : ''
|
||||
|
||||
return (
|
||||
`<?xml version="1.0" encoding="UTF-8"?>\n` +
|
||||
stylesheet +
|
||||
`<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ` +
|
||||
`xmlns:xhtml="http://www.w3.org/1999/xhtml">\n` +
|
||||
urls +
|
||||
|
||||
Reference in New Issue
Block a user