RSS verso 66 social network: Facebook, Instagram, X, LinkedIn, Telegram e altri Blog Affiliazione Contatti
Accedi Inizia gratis
Updated: 2026-10-29
Roda RSS Feed in Ruby: Route, ETag Caching and Auto-Post

Short answer: In a Roda application, an RSS feed is one route that returns an XML document built with the rss library from Ruby. Describe your posts as plain objects, build the channel and the items with the maker interface, set the RSS content type, let the caching plugin answer with a 304 status when nothing has changed, and test the live address with curl and a validator. When the address, the links and the dates stay stable, an auto-poster can read the feed and share every new post on your social networks.

Roda is a light Ruby web toolkit built around a routing tree. Instead of declaring every route in a table, you describe the path step by step inside one block, and each branch is cheap and fast. That style fits a feed well, because a feed is one small endpoint with a clear job. The risks are the usual ones: invalid dates, text that breaks the XML, links that are not absolute, a content type that makes browsers show the file as plain text and an identifier that changes when someone edits a post. This guide builds a complete route, explains each choice and ends with a test routine.

Set up the project

Create a small project with Roda, the rss gem and a server. On recent versions of Ruby the rss library is no longer part of the default install, so it must be listed in the Gemfile.

# Gemfile
source "https://rubygems.org"

gem "roda"
gem "rss"
gem "puma"

The data in this guide is a small in-memory list, which keeps the code short. In a real project the list would come from a database or from files on disk, and the rest of the code would stay the same. If you already run a larger Ruby application, compare this approach with the one in the Rails feed guide e the Sinatra feed guide.

Describe the posts

A feed item needs a title, a permanent address, a publication date and a summary. A struct is enough to carry those fields. Store the date as a UTC time, so that the time zone of the server can never change what the feed says.

# config.ru
require "roda"
require "rss"
require "time"

Post = Struct.new(:slug, :title, :summary, :published_at)

POSTS = [
  Post.new(
    "spring-opening-hours",
    "Spring Opening Hours",
    "Our new opening hours start on Monday.",
    Time.utc(2026, 10, 29, 8, 0, 0)
  )
].freeze

Build the feed with the maker

The maker interface of the rss library takes care of the XML structure and of escaping special characters, which removes the most common source of invalid feeds. You assign the channel fields once and add one item for every post.

class App < Roda
  SITE = "https://example.com".freeze

  def build_feed(posts, newest)
    feed = RSS::Maker.make("2.0") do |maker|
      maker.channel.title = "Example Journal"
      maker.channel.link = SITE
      maker.channel.description = "News and guides from Example Journal."
      maker.channel.language = "en"
      maker.channel.updated = newest

      posts.each do |post|
        url = "#{SITE}/blog/#{post.slug}/"
        maker.items.new_item do |item|
          item.title = post.title
          item.link = url
          item.guid.content = url
          item.guid.isPermaLink = true
          item.description = post.summary
          item.pubDate = post.published_at
        end
      end
    end
    feed.to_s
  end

Several details in this code are deliberate. The channel date comes from the newest post and not from the clock, so that the file does not change when nothing was published; a feed that changes on every request defeats caching, as explained in the guide to ETags and conditional requests. The identifier of each item is its permanent address, which does not change when the title does. And the address is built from a fixed site constant, so every link is absolute.

Add the route and the cache validator

The route sorts the posts, keeps the newest twenty, sets the headers and returns the document. The caching plugin adds a method that sets the ETag header and, when the request already carries the same value, stops the route and answers with a 304 status and no body.

  plugin :caching

  route do |r|
    r.get "feed.xml" do
      posts = POSTS.sort_by(&:published_at).reverse.first(20)
      newest = posts.first.published_at

      response["Content-Type"] = "application/rss+xml; charset=utf-8"
      response["Cache-Control"] = "public, max-age=300"
      r.etag("#{posts.size}-#{newest.to_i}")

      build_feed(posts, newest)
    end
  end
end

run App.freeze.app

The validator is built from the number of posts and the date of the newest one, so it changes exactly when the content changes. If you edit an old post and want clients to notice, include its modification time in the value too. A 304 answer saves bandwidth for you and for every tool that polls the feed; the meaning of the status codes is described in the guide to feed status codes. Check the exact method names of the caching plugin against the documentation of the Roda version you run.

Where each field comes from

Feed elementSource in the codeRule
Titlepost.titlePlain text, no markup
Link and identifierSITE and post.slugAbsolute, https, never changes
Publication datepost.published_at in UTCIn the past, never the current time
Descriptionpost.summaryOne or two complete sentences
Channel dateNewest published_atChanges only when a post is added
Cache validatorCount and newest dateChanges only when content changes

The identifier deserves the most attention. If the slug follows the title and someone corrects a typo, the address changes, and an auto-poster that stores identifiers will treat the old and the new address as two different posts. The causes and cures are covered in why a feed auto-posts the same article twice.

Dates and drafts

The maker converts a time object into the format that the RSS specification expects, so you do not have to format dates yourself. What you do have to control is which posts are allowed in. Filter out drafts and anything with a date in the future before you build the feed; an item dated tomorrow may be hidden by one reader and shared at the wrong time by another, and a draft that leaks into the feed will be posted on social networks before it exists on your website. A single line that keeps only published posts with a date in the past is enough, and it belongs next to the sorting step. For the effect of old dates see the guide to feed order and backdated posts.

Summaries, images and encoding

Auto-posters build the best social posts from a feed that gives them a short summary and a picture. Write a real description for every post instead of cutting the text automatically, because an automatic cut can end in the middle of a sentence and that is exactly what the auto-poster will copy. If a post has an image, add it as an enclosure or a media element with an absolute address. Whether to include the full text or only a summary is discussed in full text or excerpt in an RSS feed.

Ruby strings are UTF-8 by default, and the rss library declares UTF-8 in the XML header, so the two agree. Problems appear when text arrives from a database or a file in another encoding, or when it contains control characters that are not allowed in XML at all. Convert the text at the edge of your program and remove those characters before rendering. See why special characters and emoji break feeds.

Make the feed discoverable

Add a link element to the head of your page layout so that browsers, readers and tools can find the feed from any page, with the relation set to alternate, the type set to the RSS media type and the absolute address of the feed. A person or a tool that only knows your home page can then find the feed on its own. The full explanation is in the guide to feed autodiscovery.

Deploy behind a proxy

A Roda application usually runs behind a web server or a platform router that terminates the encrypted connection. Check three things after the first deployment. The proxy must pass the request headers that the caching logic reads, so that a conditional request reaches the application. It must not store an old copy of the feed for longer than you intend, because a feed that lags behind your website delays every social post. And the address that outsiders use must be the same one that the code writes into the links, since the application cannot guess it from an internal request. If the server compresses responses, make sure the compressed file still carries the correct content type; see the guide to feed compression.

Test the feed

Start the server and request the route locally, then test the deployed address from outside, because a local test says nothing about the proxy and the certificate in front of your application.

bundle exec rackup -p 9292
curl -i http://localhost:9292/feed.xml
curl -i https://example.com/feed.xml
curl -i -H 'If-None-Match: "1-1793260800"' https://example.com/feed.xml
  • The first two requests return 200, the RSS content type and a body that starts with an XML declaration.
  • The last request returns 304 and no body when the value matches the one the server sent.
  • Every link is absolute, uses https and points to your real domain.
  • Paste the address into the W3C Feed Validation Service and fix every error.
  • Add a post, reload the feed and confirm that the new item comes first and the validator changes.

The validator in the example is only an illustration, so copy the exact header from your own first response instead. A longer routine is in the feed validation guide, and a shorter one in debugging a feed with curl.

Common mistakes to avoid

  • The wrong content type. A default HTML type makes browsers render or download the file instead of showing XML.
  • A channel date taken from the clock. The file changes on every request and caching stops working.
  • Slugs that follow titles. An edit changes the address and the post may be shared twice.
  • Drafts or future posts in the list. An article is shared before it exists on the website.
  • Relative links. They are broken for everyone who reads the feed away from your site.
  • Only a local test. The proxy in front of the application changes headers and caching.

How PostRSS works with a Roda 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 description 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.

Letture correlate

The bottom line

A Roda feed is one route, but it has to get the details right: posts with stable slugs and dates in UTC, XML produced by a library that escapes text, absolute links, a channel date taken from the newest post, the correct content type and a validator that lets clients skip unchanged files. Keep drafts and future posts out, test the live address with curl and a validator, then connect it to an auto-poster. After that, every post you publish reaches your readers and your social channels without extra work.

FAQ

How do I return XML from a Roda route?

Set the Content-Type response header to the RSS media type and return the XML string from the route block. Roda sends the returned string as the body of the response.

Which Ruby library should I use to build the feed?

The rss library offers a maker interface that builds valid RSS and escapes special characters for you. A template written with an XML builder works too, as long as you escape text and format dates correctly.

How do I support conditional requests in Roda?

Load the caching plugin and set an ETag from a value that changes only when the content changes. When the client sends the same value back, the route stops and the server answers with a 304 status.

Why does my feed open as a web page in the browser?

The content type is probably still the default one. Set it to the RSS media type before you return the body, then check the header with curl.

Can PostRSS read a feed served by a Roda application?

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.

Altri strumenti del nostro team

Realizzati da Internet Solutions, il team dietro PostRSS. Ognuno ti fa risparmiare tempo in modo diverso.