Sitemap.xml and RSS Feed in Next.js App Router, Technical SEO 2026

sitemap6 min readJuly 20, 2026

Author: DevStudio.it

TL;DR

Google indexes faster with a complete sitemap.xml and RSS for the blog section, crawlers do not have to guess new posts. In Next.js App Router you generate both as sitemap.ts and feed.xml/route.ts without WordPress plugins. For multilingual sites (pl/en/de) each sitemap URL gets hreflang alternates, and RSS publishes only posts for that locale. Deploy on DevStudioIT Cloud serves files from CDN cache, updated on every next build. Dynamic content (case studies from PostgreSQL in Branchly) enters the sitemap via async fetch in sitemap.ts.

Who is this for

  • Next.js corporate sites with a blog and multiple language versions
  • SEO teams wanting technical control without Yoast / Rank Math
  • Projects migrating from WordPress, RSS subscribers need a new feed URL
  • Developers using ISR or static generation, sitemap must reflect revalidate
  • Anyone with a /sitemap.xml containing three URLs "because someone added them once"

Keyword

sitemap nextjs app router, rss feed next.js, feed.xml seo, sitemap.ts i18n, robots.txt nextjs 2026, hreflang sitemap

sitemap.xml, native MetadataRoute in Next.js

File app/sitemap.ts exports a function returning a URL array:

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 = ['', '/web-development', '/blog', '/contact'].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 serves the result at /sitemap.xml. Do not hand-write XML in public/, you lose typing and dynamic entries.

Field Recommendation Note
lastModified Real content change date Not new Date() for static pages every build
changeFrequency weekly / monthly Crawler hint, not a guarantee
priority 0.5–1.0 relative Does not directly affect ranking

Multilingual, hreflang in sitemap vs &lt;head&gt;

Google accepts hreflang in sitemap (extension xhtml:link) or in HTML. Next.js Metadata API in layout:

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}`,
      },
    },
  };
}

Consistency between &lt;head&gt; and sitemap matters more than picking one method alone.

Split sitemap, when URL count > 50,000

Large sites split into an index:

File Content
/sitemap.xml Index pointing to sub-sitemaps
/sitemap-pages.xml Static pages
/sitemap-blog.xml Blog posts
/sitemap-cases.xml Case studies from Branchly

Google limit: 50 MB / 50k URLs per file.

RSS / feed.xml, route handler

RSS subscribers and aggregators expect 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>Web development guides and news</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',
    },
  });
}

Final URLs: /pl/feed.xml, /en/feed.xml. Add &lt;link rel=&quot;alternate&quot; type=&quot;application/rss+xml&quot;&gt; in blog &lt;head&gt;.

robots.txt, consistency with sitemap

File 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',
  };
}
Mistake Effect
Sitemap points to staging URL Indexing test environment
disallow: /blog + active RSS Conflicting signals for Google
No sitemap in robots Slower discovery of new posts

Staging on DevStudioIT Cloud should have robots: noindex in layout metadata, separate instance, separate robots.

Dynamic Branchly content in sitemap

Case studies or CMS landings in 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 in sitemap.ts. After CMS publish the sitemap refreshes within an hour without full redeploy, acceptable tradeoff for B2B blogs.

Validation before production

Test Tool
Sitemap XML validity Google Search Console → Sitemaps
RSS valid validator.w3.org/feed
hreflang Ahrefs / Screaming Frog
Feed returns 200 curl -I https://devstudioit.com/en/feed.xml

Submit sitemap in Search Console once, Google fetches XML updates automatically.

Workflow after publishing a new blog post

Typical cycle in a DevStudio project with markdown in repo:

  1. Merge PR with new file content/blog/{locale}/{slug}.md
  2. CI builds Next.js and deploys to DevStudioIT Cloud
  3. sitemap.ts at build adds URL with lastModified from frontmatter
  4. RSS route with revalidate: 3600 shows the post in feed within an hour (or immediately after build if SSG)
  5. Optionally ping Google Indexing API for critical landings, blog usually needs sitemap only
Step Owner Time
Content publish Content / dev PR review
Build + deploy CI 5–12 min
Google discovery Crawler 1–7 days (normal)
RSS for subscriber Feed reader On next poll

Do not mass-ping Indexing API for every post, daily quota and spam risk. Sitemap + internal linking from blog list is enough for B2B.

Technical SEO checklist at launch

  • /sitemap.xml returns 200 and includes all locales
  • /pl/feed.xml, /en/feed.xml, /de/feed.xml validate in W3C validator
  • robots.ts points to production domain, not staging
  • Each post has alternates.languages in metadata
  • Staging has noindex, production does not
  • Search Console: separate property or folder per locale (depending on structure)

FAQ

One RSS for all languages or separate per locale?

Separate per locale, clearer for subscribers and aligned with channel &lt;language&gt;. Optionally a main feed with Polish posts only if 90% of audience is PL.

Should sitemap include noindex pages?

Do not add URLs with robots: noindex, you waste crawl budget. Filter blog drafts and /preview pages.

RSS vs Atom, which to choose?

RSS 2.0 has widest compatibility. Atom (application/atom+xml) optionally alongside, most readers support RSS.

How often to rebuild sitemap with 100+ posts?

With markdown in repo the sitemap generates on every build, no problem. With CMS async fetch + revalidate is enough; full rebuild only on URL structure changes.

Want sitemap and RSS in your Next.js site?

Related posts

Google Search Console and Next.js indexing, SSR, SSG and sitemap in 2026
5 min read
robots.txt in Next.js App Router, Crawl Budget and Disallow Pitfalls (2026)
5 min read
Canonical URLs and Duplicate Content in Next.js, PL/EN/DE, hreflang, Trailing Slash (2026)
5 min read

About the author

We build fast websites, web/mobile apps, AI chatbots and hosting setups — with a focus on SEO and conversion.

Recommended links

From theory to production — Branchly, our hosting stack and shipped work.

Like how we think? Let's build something together.

Start project configuration