RSS a 66 redes sociales: Facebook, Instagram, X, LinkedIn, Telegram y más Blog Afiliados Contactos
Iniciar sesión Empezar gratis
Updated: 2026-10-28
Nitro RSS Feed: An h3 Event Handler, Dates and Caching

Short answer: In Nitro, the server engine behind Nuxt and several other frameworks, an RSS feed is a server route: a file in the routes folder whose default export is an event handler that returns the XML. Name the file so that it answers at /rss.xml, read the site address from runtime configuration, build absolute links and valid dates, escape every text value, set an XML content type, wrap the handler in the built-in cache, and test the deployed address with curl and a validator. Then give the address to an auto-poster so that new posts are shared on your social networks.

Nitro is a small server framework that compiles to many targets, from a Node process to edge workers and static output. It powers Nuxt, Analog, SolidStart and TanStack Start, which means that one feed pattern works across a family of tools. Whether you use Nitro on its own for a content API or you reach it through a framework, the steps are the same: a file, a handler, a string of XML and a few headers. This guide shows how to build the feed, how to configure the address and the cache, and how to avoid the mistakes that make feeds fail in production.

Why Nitro is a good place for a feed

A feed is not a page. It needs no components, no client code and no layout, only a response with the right text and headers. A Nitro server route is exactly that. It runs on the server, can read your content from files, a database or an API, and returns a plain response. It also has a built-in cache layer, so the XML is not rebuilt on every request, and it follows the same deployment settings as the rest of your application. If you use Nuxt, the same code works in the server folder; see the Nuxt feed guide for the framework side.

Create the server route

Nitro builds routes from the files in the routes folder, and the file name becomes the path. A file named rss.xml.ts answers at /rss.xml. The example below reads the site address from configuration and gets its posts from a function that stands for your own source.

// server/routes/rss.xml.ts
import { getPosts } from "../utils/posts";

function esc(value: string) {
  return value
    .replaceAll("&", "&" + "amp;")
    .replaceAll("<", "&" + "lt;")
    .replaceAll(">", "&" + "gt;");
}

export default defineEventHandler(async (event) => {
  const site = useRuntimeConfig(event).siteUrl as string;
  const now = Date.now();

  const posts = (await getPosts())
    .filter((p) => new Date(p.date).getTime() <= now)
    .sort((a, b) => +new Date(b.date) - +new Date(a.date))
    .slice(0, 20);

  const items = posts
    .map(
      (p) => `
    <item>
      <title>${esc(p.title)}</title>
      <link>${site}/blog/${p.slug}</link>
      <guid isPermaLink="true">${site}/blog/${p.slug}</guid>
      <pubDate>${new Date(p.date).toUTCString()}</pubDate>
      <description>${esc(p.summary)}</description>
    </item>`,
    )
    .join("");

  setHeader(event, "content-type", "application/xml; charset=utf-8");
  return `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Example Journal</title>
    <link>${site}</link>
    <description>News and guides from Example Journal.</description>
    <atom:link href="${site}/rss.xml" rel="self" type="application/rss+xml" />${items}
  </channel>
</rss>`;
});

The helper names differ slightly between versions of the underlying h3 library, and the newest version moves response headers to the event object, so check the documentation of the version you use. The structure does not change: a default export, a header and a returned string. Nitro also lets you restrict a route to one method by adding .get to the file name, which is a good habit for a file that should only be read.

Configure the site address

Never hard-code the domain in the handler. A feed with the wrong address in its links is the most common reason for a feed that looks right and does not work. Put the address in the runtime configuration and let the environment override it in each deployment.

// nitro.config.ts
export default defineNitroConfig({
  runtimeConfig: {
    siteUrl: "https://example.com",
  },
});

# production environment
NITRO_SITE_URL=https://example.com

With this in place, the same build can run on a staging address and on the real domain, and each produces links that match where it runs. In a framework that wraps Nitro, the configuration lives in that framework’s own file, and the same environment convention applies with its own prefix. Make sure the value includes https and has no trailing slash, because the template adds the path.

Cache the response

Building the feed requires reading content, and a burst of requests from readers and bots should not repeat that work. Nitro includes a cache wrapper for event handlers. Replace the default export with a cached handler and choose how long the result lives.

export default defineCachedEventHandler(
  async (event) => {
    // same body as before
  },
  { maxAge: 60 * 15, swr: true, name: "rss" },
);

Fifteen minutes is a reasonable default for a blog. The swr option lets the server return the stored copy immediately while it refreshes in the background, which keeps the response fast even when your data source is slow. If you publish rarely and want updates to appear quickly, lower the time. You can also set caching rules by route in the configuration, so that the feed has a different policy from the rest of the site. The effect on tools that fetch the feed is explained in why auto-posted content sometimes lags.

Dates, drafts and the future

The pubDate of an RSS 2.0 item uses the format of an email date, and toUTCString produces a value that validators accept. Parse dates carefully. A string without a time zone is read in the zone of the server, so a post written late in the evening can slide to the next day when the code runs in a different region; store dates in UTC or with an explicit offset. Remove posts dated in the future and drafts before you build the list, as the filter above does. An auto-poster shares an item as soon as it appears in the feed, and an article that is visible in the feed but not yet on the website gives readers a broken link.

Escape the text

An RSS file is XML, and one unescaped ampersand makes it invalid. Titles with an ampersand, summaries with a less-than sign and names with accents are common sources of errors. The helper in the example escapes the three characters that matter in text content, and the file declares UTF-8. If you include full content in a content element, wrap it in a CDATA section and check that the text does not contain the closing sequence. More examples are in why special characters and emoji break auto-posted content.

Where each field comes from

Feed elementSource in your appRule
Titlepost titlePlain text, escaped
Linkruntime site address plus slugAbsolute, https, returns 200
Datepost date in UTCIn the past, never changes
Summarydescription fieldOne or two complete sentences
Identifierthe permanent linkDoes not change when the title does
Imagecover image addressAbsolute, at least 1200 pixels wide

The identifier is the field to protect. If a post gets a new slug, its link changes, and an auto-poster that stores identifiers treats the new address as a new post. Keep slugs stable and add redirects when you must rename. The full story is in why a feed auto-posts the same article twice.

Prerender the feed for static hosting

Nitro can also produce static output. If you deploy to a static host, ask Nitro to render the feed at build time by listing the route in the prerender settings, and the file is written next to your pages. The trade-off is the same as for any static site: the feed changes only when you rebuild, so schedule a rebuild after you publish, for example with a webhook from your content source or a timed job. For a server deployment, the cached handler is simpler and keeps the feed fresh without a rebuild.

// nitro.config.ts
export default defineNitroConfig({
  prerender: { routes: ["/rss.xml"] },
});

Make the feed discoverable

Add a link element to the head of your pages so that browsers, feed readers and tools can find the feed from any page. The address should be the absolute one that readers use.

<link
  rel="alternate"
  type="application/rss+xml"
  title="Example Journal"
  href="https://example.com/rss.xml"
>

The details are in the guide to feed autodiscovery.

Test before you share the address

Run the production build the way you will deploy it and look at the response before you publish anything.

node .output/server/index.mjs &
curl -i http://localhost:3000/rss.xml
curl -I https://example.com/rss.xml

Work through a short list of checks:

  • The response is a 200 with an XML content type, and the body starts with an XML declaration and an rss element.
  • Every link is absolute, uses https and points to your real domain, not localhost.
  • The newest post is first, every date is in the past and the order is the same on every request.
  • Paste the public address into the W3C Feed Validation Service and fix every error.
  • Request the file twice and confirm that the second response is served quickly from the cache.

There is a longer routine in the feed validation guide and a short one in debugging a feed with curl. If the file opens as plain text in a browser, see the content type header guide.

Common mistakes to avoid

  • A hard-coded domain. Links point to staging or localhost; read the address from configuration.
  • No content type. The feed opens as plain text, and some tools refuse it.
  • Unescaped text. One ampersand makes the file invalid.
  • No cache. Every request rebuilds the feed, and bots slow the site.
  • Future posts in the feed. An article is shared before it exists on the website.
  • Changing the address. Subscribers and auto-posters lose the feed without a warning.

How PostRSS works with a Nitro feed

PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your feed, connect your accounts and choose how each post is built from the title, the summary or the page title. PostRSS checks the feed as often as every minute and shares each new entry, with its image and link, to the channels you choose among 66 supported networks, messengers, team chats and blogs, including LinkedIn, X, Bluesky, Mastodon and Telegram. Keyword filters, UTM parameters and posting windows let you tune each channel. See the PostRSS features page and the plan limits on the pricing page. More developer guides are under the Technical Guide tag.

Lectura relacionada

The bottom line

Nitro needs one server route to publish a dependable feed: a file that answers at the feed address, a handler that builds the XML from published posts with absolute links, valid dates and escaped text, a site address read from configuration, an XML content type and a cached response. Keep the address and the slugs unchanged, make the feed discoverable, test the deployed file with curl and a validator and connect it to an auto-poster. After that, every post you publish can reach your readers and your social channels automatically.

FAQ

How do I create an RSS route in Nitro?

Add a file named rss.xml.ts to the server routes folder and export an event handler that returns the XML string with an XML content type. Nitro maps the file name to the path.

How do I cache the feed in Nitro?

Wrap the handler in the cached event handler helper and set maxAge, for example fifteen minutes. The swr option serves a stored copy while the feed is rebuilt in the background.

Does the same code work in Nuxt?

Yes. Nuxt uses Nitro for its server folder, so the same route file works there. Read the site address from the runtime configuration of the framework.

Why are my feed links pointing to localhost?

The site address is hard-coded or the environment variable is missing in production. Read it from runtime configuration and set it in each deployment.

Can PostRSS read a feed built with Nitro?

Yes. PostRSS reads any valid RSS or Atom address that is reachable without a login. Paste the address, connect your channels and new posts are shared as soon as they appear.

New guides, once a month

What changed in the networks, what broke, and how to fix it before it costs you reach.

We send a confirmation e-mail first. Unsubscribe any time.

Más herramientas de nuestro equipo

Creadas por Internet Solutions, el equipo detrás de PostRSS. Cada una te ahorra tiempo a su manera.