After publishing a post to my site, I usually cross-post it to DEV.to. Originally I would use their RSS feature and cross-posting was quite a painless process, but over time I've added new features to my blog like:
- Line highlighting and code block headings (covered in my post on Gatsby code blocks)
- MDX components
- Additional Markdown frontmatter fields that aren’t used in DEV.to
Which meant that I had to keep making manual changes to all my posts to make them ready for publishing on DEV. To save me some time, I wrote a script to automate this process.
If you've never written a script before, I've written a post on renaming files with Node.js scripts which should give you a good overview.
The cross-posting workflow I follow now is this:
- Publish a new post on my personal blog.
- Go to DEV.to and refresh my RSS feed (explained below).
- Run my
devto.js
script. - Double-check the draft on DEV.to, and then hit publish.
Hook up your RSS feed to DEV.to
I cross-post my posts to DEV.to via my site’s RSS feed. This way, I get the “Originally published at” message to show up underneath the title of my posts:
If you go to your DEV.to settings page, and click the Extensions option, you’ll have the opportunity to add an RSS feed:
Once you have hooked up your RSS feed, DEV.to will periodically check it to see if there are any new posts, and add the post in DEV as a draft.
After I publish a post on my own site, I go into DEV.to and hit the “Fetch feed now” button to make it show up straightaway. Unfortunately DEV doesn't have an API to do this step from within my script.
Run the script to update the draft post in DEV.to
To run this script, you’ll need your own DEV API key. I store mine in a .env
file in my site’s repository:
// .env
DEV_API_KEY=<key_goes_here>
The script makes use of two of the DEV API’s endpoints:
- Getting a user’s unpublished articles.
- Updating the article. The unpublished articles endpoint will give us an ID we need to use.
My posts are stored on my repository with Markdown and frontmatter, in a format like this:
---
title: "Hello! This is the markdown file"
date: 2021-09-25
tags: ["react"]
---
Content of the post goes here.
![Image with alt text](./image.png)
The script will transform it into this on DEV:
---
title: "Hello! This is the markdown file"
published: false
tags: ["react"]
---
Content of the post goes here.
![Image with alt textt](https://emgoto.com/slug/image.png)
There’s three things to point out here:
- I make sure the frontmatter has
published: false
so it stays in draft mode - I remove the
date
field. If you leave this value, DEV will set it as having been published at midnight of the date you specified. This can reduce the chance of your post actually getting views on DEV’s home page since it’s considered an “old” post. - There’s no DEV image API so you'll need to host the image yourself
The full version of the script is available on my site’s Github repository, and I’ve got a shortened version below that you can copy-paste.
#!/usr/bin/env node
const { readFile } = require('fs');
const { join } = require('path');
const glob = require('glob');
const fetch = require('node-fetch');
// I store my API key in a .env file
require('dotenv').config();
const updateArticle = (devArticleId, content) => {
fetch(`https://dev.to/api/articles/${devArticleId}`, {
method: 'PUT',
headers: {
'api-key': process.env.DEV_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
article: {
body_markdown: content,
},
}),
})
};
const updateFile = (content, slug) => {
// Update frontmatter
content = content.replace(/date: .*\n/, 'published: false\n');
// Update images
content = content.replace(
/!\[(.+?)\]\(\.\/(.+?)\)/g,
`![$1](https://emgoto.com/${slug}/$2)`,
);
// TODO: Any other changes to your content
return content;
}
const devto = () => {
// Looks for folders inside of "posts" folder that matches the given slug.
const slug = process.argv[1];
const file = [
...glob.sync(join(process.cwd(), 'posts', slug, 'index.mdx')),
][0];
readFile(file, 'utf8', (err, content) => {
if (err) reject(err);
const title = content.match(/title: "(.*)"\n/)[1];
content = updateFile(content, slug);
fetch('https://dev.to/api/articles/me/unpublished', {
headers: { 'api-key': process.env.DEV_API_KEY },
})
.then((response) => response.json())
.then((response) => {
if (response.length > 0) {
const draftTitle = response[0].title;
if (draftTitle === title) {
const devArticleId = response[0].id;
updateArticle(devArticleId, content);
}
}
})
});
};
devto();
Top comments (3)
There is something that you might find useful: the
fs
module has promise-based functions under thepromises
object.So code like this is perfectly valid.
Thanks for the tip!
Way better than my TheActionDev.
Thanks, Emma for sharing ❤️