diff --git a/api-helpers/sitemap.ts b/api-helpers/sitemap.ts new file mode 100644 index 00000000..ed272b3f --- /dev/null +++ b/api-helpers/sitemap.ts @@ -0,0 +1,79 @@ +import { + getArticlesPaginationForGrid, + getArticlesPaginationForList, + getArticlesSlugs, +} from './articles'; +import { closeConnection, openConnection } from './db'; + +type Item = { + readonly path: string; + readonly changefreq: 'daily' | 'monthly' | 'always' | 'hourly' | 'weekly' | 'yearly' | 'never'; + readonly priority: number; +}; + +const staticItems: readonly Item[] = [ + { path: '/', changefreq: 'hourly', priority: 1 }, + { path: '/o-serwisie', changefreq: 'monthly', priority: 0.5 }, + { path: '/zglos-serwis', changefreq: 'monthly', priority: 0.5 }, + + { path: '/list', changefreq: 'hourly', priority: 0.9 }, + { path: '/grid', changefreq: 'hourly', priority: 0.9 }, +]; + +export async function getSitemap() { + try { + const prisma = await openConnection(); + + const [gridCursors, listCursors, articleSlugs] = await Promise.all([ + getArticlesPaginationForGrid(prisma), + getArticlesPaginationForList(prisma), + getArticlesSlugs(prisma), + ]); + + const dynamicItems: readonly Item[] = [ + ...articleSlugs.map(({ slug }) => ({ + path: `/artykuly/${slug}`, + changefreq: 'monthly' as const, + priority: 0.5, + })), + ...gridCursors.map((cursor) => ({ + path: `/grid/${cursor}`, + changefreq: 'hourly' as const, + priority: 0.4, + })), + ...listCursors.map((cursor) => ({ + path: `/list/${cursor}`, + changefreq: 'hourly' as const, + priority: 0.4, + })), + ]; + + return sitemapXml([...staticItems, ...dynamicItems]); + } finally { + await closeConnection(); + } +} + +function itemsToXml(items: ReadonlyArray) { + return items + .map((item) => + ` + + https://${process.env.NEXT_PUBLIC_URL!}${item.path} + ${item.changefreq} + ${item.priority} + + `.trim(), + ) + .join('\n'); +} + +function sitemapXml(items: ReadonlyArray) { + const xml = itemsToXml(items); + return ` + + + ${xml} + + `.trim(); +} diff --git a/next.config.js b/next.config.js index 2a6002b7..740755f4 100644 --- a/next.config.js +++ b/next.config.js @@ -105,6 +105,10 @@ config.rewrites = async () => { source: '/feed', destination: '/api/feed', }, + { + source: '/sitemap.xml', + destination: '/api/sitemap', + }, ]; }; diff --git a/pages/api/sitemap.ts b/pages/api/sitemap.ts new file mode 100644 index 00000000..a52841f3 --- /dev/null +++ b/pages/api/sitemap.ts @@ -0,0 +1,21 @@ +import Boom from '@hapi/boom'; + +import { withAsync } from '../../api-helpers/api-hofs'; +import { getSitemap } from '../../api-helpers/sitemap'; +import { REVALIDATION_TIME } from '../[displayStyle]/[cursor]'; + +export default withAsync(async (req, res) => { + if (req.method !== 'GET') { + throw Boom.notFound(); + } + + const sitemap = await getSitemap(); + + res.setHeader('Content-Type', 'application/xml; charset=utf-8'); + // revalidate a bit less frequently than usual + res.setHeader('Cache-Control', `s-maxage=${REVALIDATION_TIME * 4}, stale-while-revalidate`); + + res.send(sitemap); + + return null; +}); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 00000000..4c9a0254 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Disallow: /admin + +Sitemap: https://polskifrontend.pl/sitemap.xml