TL;DR
Google indeksuje szybciej, gdy ma sitemap.xml z pełną listą URL i RSS dla sekcji bloga — crawler nie musi zgadywać nowych wpisów. W Next.js App Router generujesz oba pliki jako sitemap.ts i feed.xml/route.ts (lub dedykowany rss.xml/route.ts) bez pluginów WordPress. Dla strony wielojęzycznej (pl/en/de) każdy URL w sitemap dostaje hreflang alternates, a RSS publikuje tylko wpisy z danego locale. Deploy na DevStudioIT Cloud serwuje pliki z cache CDN — aktualizacja po każdym next build. Treści dynamiczne (case studies z PostgreSQL w Branchly) wchodzą do sitemap przez async fetch w sitemap.ts.
Dla kogo to jest
- Stron firmowych Next.js z blogiem i wieloma wersjami językowymi
- Zespołów SEO, które chcą technicznej kontroli bez Yoast / Rank Math
- Projektów migrujących z WordPress — RSS subskrybenci muszą dostać nowy feed URL
- Developerów wdrażających ISR lub static generation — sitemap musi odzwierciedlać
revalidate - Każdego, kto ma
/sitemap.xmlz trzema URL-ami „bo ktoś kiedyś dodał"
Fraza (SEO)
sitemap nextjs app router, rss feed next.js, feed.xml seo, sitemap.ts wielojęzyczność, robots.txt nextjs 2026, hreflang sitemap
sitemap.xml — natywny MetadataRoute w Next.js
Plik app/sitemap.ts eksportuje funkcję zwracającą tablicę URL:
import type { MetadataRoute } from 'next';
import { getBlogPosts } from '@/lib/blog';
const BASE = 'https://devstudioit.com';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const staticRoutes = ['', '/strony-www', '/blog', '/kontakt'].flatMap((path) =>
['pl', 'en', 'de'].map((locale) => ({
url: `${BASE}/${locale}${path}`,
lastModified: new Date(),
changeFrequency: 'weekly' as const,
priority: path === '' ? 1 : 0.8,
}))
);
const posts = await getBlogPosts();
const blogEntries = posts.map((post) => ({
url: `${BASE}/${post.locale}/blog/${post.slug}`,
lastModified: new Date(post.updated ?? post.date),
changeFrequency: 'monthly' as const,
priority: 0.6,
}));
return [...staticRoutes, ...blogEntries];
}Next.js serwuje wynik pod /sitemap.xml. Nie twórz ręcznie pliku XML w public/ — tracisz typowanie i dynamiczne wpisy.
| Pole | Zalecenie | Uwaga |
|---|---|---|
lastModified |
Data realnej zmiany treści | Nie new Date() dla statycznych stron co build |
changeFrequency |
weekly / monthly |
Hint dla crawlera, nie gwarancja |
priority |
0.5–1.0 względne | Nie wpływa bezpośrednio na ranking |
Wielojęzyczność — hreflang w sitemap vs w <head>
Google akceptuje hreflang w sitemap (extension xhtml:link) lub w HTML. Next.js Metadata API w layoutcie:
export async function generateMetadata({ params }): Promise<Metadata> {
const { locale, slug } = await params;
return {
alternates: {
canonical: `https://devstudioit.com/${locale}/blog/${slug}`,
languages: {
pl: `https://devstudioit.com/pl/blog/${slug}`,
en: `https://devstudioit.com/en/blog/${slug}`,
de: `https://devstudioit.com/de/blog/${slug}`,
},
},
};
}Dla sitemap z hreflang użyj rozszerzonego formatu (Next.js 15 wspiera alternates w obiekcie sitemap entry, gdy dodasz pole zgodne z dokumentacją). Spójność między <head> a sitemap jest ważniejsza niż wybór jednej metody.
Podział sitemap — gdy URL > 50 000
Duże serwisy dzielą sitemap na indeks:
| Plik | Zawartość |
|---|---|
/sitemap.xml |
Indeks wskazujący pod-sitemapy |
/sitemap-pages.xml |
Strony statyczne |
/sitemap-blog.xml |
Wpisy bloga |
/sitemap-cases.xml |
Case studies z Branchly |
W Next.js: app/sitemap/[id]/route.ts lub wiele plików sitemap.ts w segmentach — sprawdź limit 50 MB / 50k URL na plik według Google.
RSS / feed.xml — route handler
Subskrybenci RSS, agregatory branżowe i niektóre narzędzia monitoringu oczekują application/rss+xml. Route handler:
// app/[locale]/feed.xml/route.ts
import { getBlogPosts } from '@/lib/blog';
export async function GET(
_req: Request,
{ params }: { params: Promise<{ locale: string }> }
) {
const { locale } = await params;
const posts = await getBlogPosts(locale);
const site = `https://devstudioit.com/${locale}`;
const items = posts.slice(0, 20).map((post) => `
<item>
<title><![CDATA[${post.title}]]></title>
<link>${site}/blog/${post.slug}</link>
<guid isPermaLink="true">${site}/blog/${post.slug}</guid>
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
<description><![CDATA[${post.description}]]></description>
</item>
`).join('');
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>DevStudio.it Blog (${locale})</title>
<link>${site}/blog</link>
<description>Aktualności i przewodniki web development</description>
<language>${locale}</language>
${items}
</channel>
</rss>`;
return new Response(xml, {
headers: {
'Content-Type': 'application/rss+xml; charset=utf-8',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
},
});
}URL końcowy: /pl/feed.xml, /en/feed.xml. Dodaj <link rel="alternate" type="application/rss+xml"> w <head> bloga.
robots.txt — spójność z sitemap
Plik app/robots.ts:
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api/', '/admin/'],
},
sitemap: 'https://devstudioit.com/sitemap.xml',
};
}| Błąd | Skutek |
|---|---|
| Sitemap wskazuje na staging URL | Indeksacja środowiska testowego |
disallow: /blog + RSS aktywny |
Sprzeczne sygnały dla Google |
| Brak sitemap w robots | Wolniejsze odkrywanie nowych wpisów |
Staging na DevStudioIT Cloud powinien mieć robots: noindex w metadata layoutu — osobna instancja, osobny robots.
Treści dynamiczne z Branchly w sitemap
Case studies lub landingi CMS trzymane w PostgreSQL (Branchly):
const cases = await db.caseStudy.findMany({
where: { published: true },
select: { slug: true, updatedAt: true, locale: true },
});
const caseEntries = cases.map((c) => ({
url: `${BASE}/${c.locale}/case-studies/${c.slug}`,
lastModified: c.updatedAt,
}));Cache: export const revalidate = 3600 w sitemap.ts lub fetch z { next: { revalidate: 3600 } }. Po publikacji w CMS sitemap odświeży się w ciągu godziny bez pełnego redeploy — tradeoff akceptowalny dla bloga B2B.
Walidacja przed produkcją
| Test | Narzędzie |
|---|---|
| Poprawność XML sitemap | Google Search Console → Sitemaps |
| RSS valid | validator.w3.org/feed |
| hreflang | Ahrefs / Screaming Frog |
| Czy feed zwraca 200 | curl -I https://devstudioit.com/pl/feed.xml |
Po deploy na produkcję zgłoś sitemap w Search Console raz — kolejne aktualizacje XML Google pobiera sam.
Workflow po publikacji nowego wpisu bloga
Typowy cykl w projekcie DevStudio z markdown w repo:
- Merge PR z nowym plikiem
content/blog/{locale}/{slug}.md - CI buduje Next.js i deploy na DevStudioIT Cloud
sitemap.tsprzy buildzie dodaje URL zlastModifiedz frontmatter- RSS route z
revalidate: 3600pokazuje wpis w feedzie w ciągu godziny (lub od razu po buildzie jeśli SSG) - Opcjonalnie ping do Google Indexing API dla krytycznych landingów — blog zwykle wystarczy sitemap
| Krok | Kto | Czas |
|---|---|---|
| Publikacja treści | Content / dev | PR review |
| Build + deploy | CI | 5–12 min |
| Odkrycie przez Google | Crawler | 1–7 dni (normalne) |
| RSS u subskrybenta | Feed reader | Przy następnym poll |
Nie pinguj Indexing API masowo dla każdego wpisu — limit dzienny i ryzyko flagi spam. Sitemap + internal linking z listy bloga wystarczy dla B2B.
Checklist SEO technicznego przy launchu
-
/sitemap.xmlzwraca 200 i zawiera wszystkie locale -
/pl/feed.xml,/en/feed.xml,/de/feed.xmlwalidują się w W3C validator -
robots.tswskazuje produkcyjną domenę, nie staging - Każdy wpis ma
alternates.languagesw metadata - Staging ma
noindex, produkcja nie - Search Console: osobna property lub folder per locale (zależnie od struktury)
FAQ
Jeden RSS dla wszystkich języków czy osobny per locale?
Osobny per locale — czytelniejszy dla subskrybenta i zgodny z <language> w kanale. Opcjonalnie główny feed z wpisami tylko po polsku, jeśli 90% audience to PL.
Czy sitemap musi zawierać noindex strony?
Nie dodawaj URL z robots: noindex — marnujesz crawl budget. Filtruj drafty bloga i strony /preview.
RSS vs Atom — co wybrać?
RSS 2.0 ma najszerszą kompatybilność. Atom (application/atom+xml) opcjonalnie obok — większość czytników obsługuje RSS.
Jak często rebuild sitemap przy 100+ wpisach?
Przy markdown w repo sitemap generuje się przy każdym buildzie — zero problemu. Przy CMS async fetch + revalidate wystarczy; pełny rebuild tylko przy zmianie struktury URL.
Chcesz sitemap i RSS w swoim Next.js?
- Skontaktuj się z nami — skonfigurujemy sitemap, hreflang, RSS i Search Console pod wielojęzyczny blog
- JSON-LD Schema.org — uzupełnienie technicznego SEO obok sitemap
- DevStudioIT Cloud — hosting z cache pod statyczne XML
O autorze
Budujemy szybkie strony WWW, aplikacje web/mobile, chatboty AI i hosting — z naciskiem na SEO i konwersję.
Przydatne linki
Od teorii do produkcji — Branchly, hosting i realizacje.
