Back to blog posts

How to cross-post to Dev.to with an RSS Feed of your NextJS website?

Cover Image for How to cross-post to Dev.to with an RSS Feed of your NextJS website?
Dylan Ballandras
Dylan Ballandras

I've been using NextJS since two years now for my professional website/blog. It's really intuitive how to do things as you can do everything from the ground up with NodeJS. I deploy my website to Vercel and use a simple yarn build command for it (doing yarn extract:i18n && yarn compile:i18n && yarn build:rss && yarn build:sitemap && next build behind the scene). Today, I will share what is the simple script behind the build:rss command.

import fs from 'fs';
import path from 'path';
import RSS from 'rss';
import siteMeta from 'site.config.js';
import {getAllPosts, getPostBySlug} from '@lib/api';
import markdownToHtml from '@lib/markdownToHtml.mjs';

const {title, description, siteUrl, author} = siteMeta;

const feed = new RSS({
  title,
  description,
  feed_url: `${siteUrl}/feed.xml`,
  site_url: siteUrl,
  webMaster: `no-reply@kayneth.dev (${author.name})`,
  language: 'en',
});

const allPosts = getAllPosts(['slug'], process.env.DEFAULT_LANGUAGE);

const feedItemsPromises = allPosts.map(async post => {
  const {title, content, slug, date} = getPostBySlug(
    post.slug,
    ['title', 'slug', 'date', 'content'],
    process.env.DEFAULT_LANGUAGE
  );

  const description = await markdownToHtml(content, true);
  return {
    title,
    description: description,
    url: `${siteUrl}/blog/${slug}`,
    date,
  }
});

Promise.allSettled(feedItemsPromises)
  .then(feedItems => feedItems.forEach(item => feed.item(item.value)))
  .then(() => {
    const xml = feed.xml();

    fs.writeFileSync(path.join('./public', 'feed.xml'), xml);
  });

I export only my blog posts. You might consider modifying this script to adapt to your need and your project structure. Talking about my project structure, I've tried to write an api.js adapted from the default NextJS blog template but for multiple languages. I'm currently writing in English and French as I want my content to be accessible to both international and French audience. My blog posts are located in _posts directory. And looks like this:

$ tree _posts
_posts
├── a-french-blog-post.mdx # using frontmatter to define the language
├── a-multilingual-post
│   ├── index.en.md
│   └── index.fr.md
└── a-blog-post-in-english.mdx

And my script looks like this and might look ugly for NodeJS developers (sorry):

import fs from 'fs';
import {join} from 'path';
import matter from 'gray-matter';

const postsDirectory = join(process.cwd(), '_posts');
const pagesDirectory = join(process.cwd(), '_pages');

export function getPostSlugs() {
  return fs.readdirSync(postsDirectory);
}

export function getPostBySlug(
  slug,
  fields = [],
  locale = process.env.DEFAULT_LANGUAGE
) {
  const realSlug = slug.replace(/\.md(x)?$/, '');
  let fullPath = join(postsDirectory, realSlug);
  let localeInPath = false;

  if (fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory()) {
    localeInPath = true;
    fullPath = join(postsDirectory, `${realSlug}`, `index.${locale}`);
  }

  fullPath = `${fullPath}.md`;

  if (!fs.existsSync(fullPath)) {
    fullPath += 'x';

    if (!fs.existsSync(fullPath)) {
      return null;
    }
  }

  const fileContents = fs.readFileSync(fullPath, 'utf8');
  const {data, content} = matter(fileContents);

  if (data['language'] && locale !== data['language']) {
    return null;
  } else if (
    !data.hasOwnProperty('language') &&
    locale !== process.env.DEFAULT_LANGUAGE &&
    !localeInPath
  ) {
    return null;
  }

  const items = {};

  // Ensure only the minimal needed data is exposed
  fields.forEach(field => {
    if (field === 'slug') {
      items[field] = realSlug;
    }
    if (field === 'content') {
      items[field] = content;
    }

    if (data[field]) {
      items[field] = data[field];
    }
  });

  return items;
}

export function getPage(slug) {
  const realSlug = slug;
  const fullPath = join(pagesDirectory, realSlug);

  if (!fs.existsSync(fullPath)) {
    return {content: null};
  }

  const fileContents = fs.readFileSync(fullPath, 'utf8');
  const {data, content} = matter(fileContents);

  return {content: content};
}

export function getAllPosts(
  fields = [],
  locale = process.env.DEFAULT_LANGUAGE,
  maxLimit = null
) {
  const slugs = getPostSlugs();

  const posts = slugs
    .map(slug => getPostBySlug(slug, fields, locale))
    .filter(i => null !== i)
    // sort posts by date in descending order
    .sort((post1, post2) => (post1.date > post2.date ? -1 : 1));

  if (maxLimit) {
    return posts.slice(0, maxLimit);
  }

  return posts;
}

Finally, both those scripts with my project architecture with generate feed.xml available to the web after building it. You can now add this URL to your dev.to profile configuration under Publishing to DEV Community from RSS.

I sincerely hope It will help you cross-publishing to dev.to or any other platform! Thank you for your time 😁 🙏