RSS ke 66 jejaring sosial: Facebook, Instagram, X, LinkedIn, Telegram, dan lainnya Blog Afiliasi Kontak
Masuk Mulai gratis
Updated: 2026-10-26
Analog RSS Feed: Angular Server Route, Dates and ETag

Short answer: To add an RSS feed to an Analog application, create a server route file named feed.xml.ts in src/server/routes that exports a defineEventHandler function, load your published posts, build the document with the rss package so every value is escaped, and return the XML with the application/rss+xml content type. Use the publication date of each post, set the channel date from the newest post, hash the finished body into an ETag so repeat requests end in a 304, add a link element to index.html, test the feed with curl and a validator, and give the public address to an auto-poster so new posts are shared automatically.

Analog is the full-stack meta-framework for Angular. It adds file-based routing, server-side rendering, static site generation and API routes to an Angular project, and it uses Nitro, the same server engine as Nuxt, for everything that runs on the server. That is good news for anyone who needs a feed. An Angular component tree is the wrong tool for producing XML, but a Nitro route is exactly the right one: a small function that receives a request and returns a response. This guide shows a complete Analog RSS feed with correct dates, stable identifiers and conditional requests, explains each decision and ends with the checks that keep the feed dependable once other systems rely on it.

When an Analog site needs a feed

A feed is worth adding whenever new items appear on a schedule you do not control: a blog, a changelog, a directory of listings or a library of guides. Readers who follow it in a feed reader get every item in order. Search engines and aggregators discover new pages faster. And an auto-poster such as PostRSS can share each new item on social networks the moment it is published, which turns a quiet publishing routine into a steady stream of visitors.

If your content already lives in a CMS that provides a feed, use that feed instead. Build the route yourself when the content comes from your own database, from markdown files or from an API without a ready-made feed. If you use Angular without Analog, with its own Express server for server-side rendering, the same ideas apply and are shown in the Express RSS feed guide. The equivalent for the Vue world is the Nuxt guide, which uses the same engine underneath.

How server routes work in Analog

Analog looks for server code in the src/server folder. Files in src/server/routes become endpoints, and the file name sets the address: a file called feed.xml.ts is served at /feed.xml, in the same way that a file called hello.ts would be served at /hello. Analog also reserves the /api prefix for files placed in src/server/routes/api, which is the usual home for JSON endpoints used by your Angular code. A feed is a public document and not part of your application programming interface, so putting it at the root, next to where a sitemap would be, is the cleaner choice.

Each file exports a default event handler created with defineEventHandler. The handler receives an event object, and a set of helper functions from the h3 library lets you read request headers, set response headers and send a status. Whatever the handler returns becomes the body. This is a different world from Angular components, so nothing in this guide touches your routes, services or templates.

Set up the project

Install the rss package, which writes valid RSS 2.0 and escapes every value on the way out.

npm install rss
npm install --save-dev @types/rss

The next step is the data. The example reads a JSON list that is created before the build, which keeps the server code free of file system access and lets Nitro bundle the data into the server output. If your posts are markdown files, a small script can read their front matter and write the file; if they come from a database or a headless CMS, replace the import with a query. The condition on the publication time matters: without it, a post scheduled for tomorrow would appear in the feed today, and an auto-poster would share it before it exists on the website.

// src/server/lib/posts.ts
import posts from '../data/posts.json';

export interface Post {
  slug: string;
  title: string;
  summary: string;
  publishedAt: string; // ISO 8601, in UTC
}

export function getPublishedPosts(limit = 20): Post[] {
  const now = Date.now();
  return (posts as Post[])
    .filter((post) => new Date(post.publishedAt).getTime() <= now)
    .sort((a, b) => b.publishedAt.localeCompare(a.publishedAt))
    .slice(0, limit);
}

The data file needs resolveJsonModule enabled in the TypeScript configuration of the server code, which is the default in most Analog templates. The sort compares ISO strings, which order correctly as long as every date is written in the same UTC format.

The complete server route

Create the file src/server/routes/feed.xml.ts. The code builds the document, computes a validator and answers conditional requests.

// src/server/routes/feed.xml.ts
import { createHash } from 'node:crypto';
import {
  defineEventHandler,
  getRequestHeader,
  sendNoContent,
  setHeader,
} from 'h3';
import RSS from 'rss';
import { getPublishedPosts } from '../lib/posts';

const SITE = 'https://example.com';

function etagMatches(header: string | undefined, etag: string): boolean {
  if (!header) return false;
  return header.split(',').some((token) => {
    const value = token.trim().replace(/^W[/]/, '');
    return value === '*' || value === etag;
  });
}

export default defineEventHandler((event) => {
  const posts = getPublishedPosts(20);
  const newest = posts.length ? new Date(posts[0].publishedAt) : new Date(0);

  const feed = new RSS({
    title: 'Example Journal',
    description: 'News and guides from Example Journal.',
    feed_url: SITE + '/feed.xml',
    site_url: SITE + '/',
    language: 'en',
    pubDate: newest,
  });

  for (const post of posts) {
    const url = SITE + '/blog/' + encodeURIComponent(post.slug) + '/';
    feed.item({
      title: post.title,
      description: post.summary,
      url,
      guid: url,
      date: new Date(post.publishedAt),
    });
  }

  const body = feed.xml();
  const etag = '"' + createHash('sha256').update(body).digest('hex').slice(0, 32) + '"';

  setHeader(event, 'ETag', etag);
  setHeader(event, 'Cache-Control', 'public, max-age=300');

  if (etagMatches(getRequestHeader(event, 'if-none-match'), etag)) {
    return sendNoContent(event, 304);
  }

  setHeader(event, 'Content-Type', 'application/rss+xml; charset=utf-8');
  return body;
});

Start the development server and open /feed.xml. The browser should show the XML or offer it for download, and the response should carry the content type, the ETag and the cache header. The h3 library also offers a ready-made helper for conditional requests, handleCacheHeaders, which you can use instead of the manual comparison once you are comfortable with how it works; the manual version is shown here because it makes each step visible.

Why the channel date matters

Notice the pubDate option passed to the rss package. The package writes it into the channel as the publication and last build date, and a value that is not provided falls back to the current time. That default is convenient for a quick test and harmful for a feed that uses an ETag. The hash is computed from the body, so the body must not change unless the content changes. If the channel date were the clock, the hash would change on every request, no client would ever receive a 304 and the validator would be useless. Passing the date of the newest post keeps the body stable between publications. The same rule applies to any value generated at request time.

How the ETag logic works

Auto-posters, readers and search engines request a feed often, and almost always nothing has changed. A validator lets them ask a cheap question: has the document changed since the version I hold? The handler hashes the finished body, quotes the hash as an entity tag and compares it with the If-None-Match header. A match produces a 304 response with no body, which saves bandwidth for both sides and keeps your server calm. A compressing proxy may turn a strong tag into a weak one, which is why the comparison strips the W/ prefix.

Where each field comes from

Feed elementSourceRule
titlePost titlePlain text, escaped by the library
linkPublic address of the pageAbsolute, with https, returning 200 directly
guidSame address, or a stored identifierNever changes after publication
pubDatePublication dateA real date, never the current time
descriptionShort summaryOne or two sentences of plain text
channel pubDateNewest post dateChanges only when content changes

The identifier is the most important decision. An auto-poster remembers which items it has already shared by their GUID. If the value changes when someone edits a slug or republishes an article, the same story is shared again. If you may rename slugs, store a separate permanent identifier and pass it as the guid of each item.

Make the feed discoverable

Add a link element to the head of src/index.html so browsers, feed readers and tools can find the feed from any page: a link with rel set to alternate, type set to application/rss+xml, the title of your site and href set to /feed.xml. Because Angular uses index.html as the shell for every route, one line covers the whole site. With that in place, a visitor or an auto-poster that only knows your home page can locate the feed itself. The details are in the guide to feed autodiscovery.

Prerendering, deployment and caching

Analog can render pages on the server, prerender them at build time or do both. A server route runs at request time on every deployment target that supports a server, which is the case for Node, serverless platforms and edge runtimes through Nitro presets, so the feed is always as fresh as your data. If you deploy a fully static site, there is no server to answer the request, and the feed has to be written as a file at build time and refreshed by a rebuild whenever you publish.

The Cache-Control header in the example lets shared caches keep the response for five minutes, and the ETag lets everyone revalidate cheaply after that. If you place a content delivery network in front of the site, keep the lifetime short so a new post does not wait long for its turn. A feed that lags behind the website by an hour defeats the purpose of automation. For the background, read how auto-posting tools use ETags and conditional requests dan why auto-posts are delayed and how to fix it.

Test the feed before you connect it

A few minutes of testing prevents days of confusion later. Check the running site from the outside:

  • Run curl -i https://example.com/feed.xml and confirm a 200 status, the right content type and an ETag header.
  • Repeat the request with the tag you received in an If-None-Match header and confirm that the answer is 304.
  • Paste the public address into the W3C Feed Validation Service and fix every error.
  • Publish a test post, wait for the cache lifetime and confirm that it appears once, at the top.
  • Change the title of an existing post and confirm that its GUID stays the same.

There is a longer checklist in the feed validation guide and a short curl routine in the guide to debugging a feed with curl.

Common mistakes to avoid

  • Putting the file under the api folder by habit. The feed would be served at /api/feed.xml, an address you probably do not want to publish.
  • Leaving the channel date at its default. The current time changes the hash on every request and disables the 304 answer.
  • Unstable GUIDs. Changing them makes auto-posters share old stories again; see why a feed auto-posts the same article twice.
  • Future posts in the feed. Always filter on the publication time.
  • Wrong content type. A feed sent as text or HTML confuses some readers; see the content type header guide.
  • A static deployment without a rebuild. The file never changes, and new posts never appear.

How PostRSS works with an Analog feed

PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your feed route, connect your accounts and choose how each post is built from the title, the description or the page title. PostRSS checks the feed as often as every minute and shares each new item, 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, and webhooks connect PostRSS to your own systems. See the PostRSS features page and the plan limits on the pricing page. More developer guides are under the Technical Guide tag.

Bacaan terkait

The bottom line

Analog needs only a Nitro server route, a data function and a feed library to serve a dependable RSS feed: select published posts dated in the past, let the library escape every value, use a stable GUID, set the channel date from the newest post and return the XML with the right content type. Add a hash-based ETag so repeat requests end in a 304, link the feed from index.html, test it with curl and a validator, keep the address unchanged and connect it to an auto-poster. After that, every post you publish can reach your social channels automatically.

FAQ

Does Analog have a built-in RSS generator?

No. Analog has no feed support, but a server route in src/server/routes can return any XML you build, and a library such as rss produces valid RSS from a few method calls.

Where do I put the feed file in an Analog project?

Create src/server/routes/feed.xml.ts and export a default event handler. The file name becomes the address, so the feed is served at /feed.xml.

Why does my feed never return 304?

Most often the body changes between requests, for example because the channel date is the current time. Pass the date of the newest post and the hash stays the same until the content changes.

Does this work with a static Analog site?

Not as a live route, because a static site has no server. Write the feed file at build time and rebuild whenever you publish, otherwise the feed will be out of date.

Can I use this feed with PostRSS?

Yes. PostRSS reads any valid RSS or Atom address that is reachable without a login. Paste the address, connect your channels and new items 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.

Alat lain dari tim kami

Dibuat oleh Internet Solutions, tim di balik PostRSS. Masing-masing menghemat waktu Anda dengan caranya sendiri.