withastro · GitHub

Sharing my current solution here as well (Astro 5). The one from @delucis did not work for me because node complained about the imports. So this uses slightly different imports, which work for me. Also I use sanitize-html instead of ultrahtml as that one left in way too many tags and attributes and the filter options were cumbersome (no reasonable defaults).

Besides that, it should be mostly the same. Some code and comments is also just straight up copied from there :-)

import { getCollection, render } from "astro:content";
import { SITE } from "@/config";
import mdxRenderer from "@astrojs/mdx/server.js";
import reactRenderer from "@astrojs/react/server.js";
import rss, { type RSSFeedItem } from "@astrojs/rss";
import type { APIContext } from "astro";
import { experimental_AstroContainer as AstroContainer } from "astro/container";
import sanitizeHtml from "sanitize-html";
export async function GET(context: APIContext) {
  // Get the URL to prepend to relative site links. Based on `site` in `astro.config.mjs`.
  let baseUrl = context.site?.href || "https://*****.de";
  if (baseUrl.at(-1) === "/") baseUrl = baseUrl.slice(0, -1);
  // Create a new Astro container that we can render components with.
  // See https://docs.astro.build/en/reference/container-reference/
  const container = await AstroContainer.create();
  // Load MDX and React renderer.
  // Other renderers for UI frameworks (e.g. Vue, etc.) would need adding here if you were using those.
  container.addServerRenderer({
    renderer: mdxRenderer,
  });
  container.addServerRenderer({
    renderer: reactRenderer,
  });
  container.addClientRenderer({
    name: "@astrojs/react",
    entrypoint: "@astrojs/react/client.js",
  });
  // Load the published posts to add to our RSS feed.
  const posts = (
    await getCollection("posts", ({ data }) => Boolean(data.published))
  ).sort((a, b) =>
    // Satisfy TypeScript by checking for `undefined` first.
    !a.data.published || !b.data.published
      ? -1 // Sort by published date, descending.
      : b.data.published > a.data.published
        ? 1
        : -1,
  );
  // Loop over blog posts to create feed items for each, including full content.
  const feedItems: RSSFeedItem[] = await Promise.all(
    posts.map(async (post) => {
      const feedItem: RSSFeedItem = {
        title: post.data.title,
        description: post.data.description,
        pubDate: post.data.published,
        link: `/posts/${post.id}/`,
      };
      // Get the `<Content/>` component for the current post.
      const { Content } = await render(post);
      // Use the Astro container to render the content to a string.
      const postHtml = await container.renderToString(Content);
      // Process and sanitize the raw content with sanitize-html.
      // Also make sure that relative links are converted to absolute links.
      const sanitizedHtml = sanitizeHtml(postHtml, {
        allowedTags: [...sanitizeHtml.defaults.allowedTags, "img"],
        transformTags: {
          a: (tagName, attribs) => ({
            tagName,
            attribs: {
              ...attribs,
              ...(attribs.href && {
                href: attribs.href.startsWith("/")
                  ? baseUrl + attribs.href
                  : attribs.href,
              }),
            },
          }),
          img: (tagName, attribs) => ({
            tagName,
            attribs: {
              ...attribs,
              ...(attribs.src && {
                src: attribs.src.startsWith("/")
                  ? baseUrl + attribs.src
                  : attribs.src,
              }),
              ...(attribs.href && {
                href: attribs.href.startsWith("/")
                  ? baseUrl + attribs.href
                  : attribs.href,
              }),
            },
          }),
        },
      });
      feedItem.content = sanitizedHtml;
      return feedItem;
    }),
  );
  return rss({
    title: SITE.title,
    description: SITE.description,
    site: baseUrl,
    items: feedItems,
    customData: "<language>en-us</language>",
    stylesheet: "/assets/styles/rss.xsl",
  });
}

Read the original on github.com ↗