DEV Community

Max Ivanov
Max Ivanov

Posted on • Updated on • Originally published at maxivanov.io

Cross-post from your blog to DEV.to (Node.js script)

DEV.to is a great place for a technical blog. The website feels lightweight and easy to navigate and the community is welcoming.
Still, you may want to publish your content under your own domain first which you have full control of. You then may want to cross-post to DEV with a link to the canonical URL.

When I started writing initially my workflow was like this:

  • Write a blog post in the comfort of my text editor locally
  • Publish to my personal blog
  • Cross-post to DEV manually

Last part definitely called for automation. There's a way to Publish from RSS but I realized some tweaks had to be made to the content before it could be published on DEV.

Another approach is to use the DEV API. My blog is built with Eleventy and I've added a little helper npm script to help with cross-posting. It loads a blog post from the markdown file, publishes a draft at DEV and returns a URL of the draft. There you can make sure it looks alright (occasionally I may need to adjust tags) and hit Publish.

Workflow now looks like this:

  • Write a blog post in the comfort of my text editor locally
  • Publish to my personal blog
  • Run npm run <path-to-md-file> → follow the draft link to review → Publish

Cross-post to DEV.to with Node.js script

If it sounds useful, below is a (beginner-friendly) guide of how to add such script to your own blog.

Create a DEV API key

In your DEV profile, go to SettingsAccountDEV Community API Keys

Give the key a name (e.g. publish-to-dev) and generate it.

Copy the key and put it in the .env file in the root of your blog. Make sure this file is listed in .gitignore as we don't want secrets to land in a git repository.

.env

DEV_TO_API_KEY=your-api-key
Enter fullscreen mode Exit fullscreen mode

npm script to publish to DEV

If not installed, you will need to add these packages to dev dependencies:

npm i --save-dev dotenv gray-matter node-fetch
Enter fullscreen mode Exit fullscreen mode

You can find the entire script here: https://github.com/maximivanov/maxivanov.io/blob/main/scripts/publish-to-devto.js

To start, we load the .env file, include dependencies and configure some settings.

./scripts/publish-to-devto.js

require('dotenv').config() // make the API key available as an environment variable

const matter = require('gray-matter') // library to parse front-matter and content from posts' markdown files
const fetch = require('node-fetch')

const apiKey = process.env.DEV_TO_API_KEY
const apiUrl = 'https://dev.to/api/articles'
const siteUrl = 'https://www.maxivanov.io' // hostname of the blog
const autoPublish = false // whether we want to publish or create drafts
Enter fullscreen mode Exit fullscreen mode

Main body of the script, which fetches the data, sends the API request and prints the draft URL:

./scripts/publish-to-devto.js

...

;(async () => {
    const path = process.argv.slice(2)[0] // read file name from command line arguments
    const file = matter.read(path)

    const payload = getPayload(file) // get payload to publish to DEV API (see below)
    const article = await publish(payload)

    if (autoPublish) {
        console.log(`Article published: ${article.url}`)
    } else {
        console.log(`Article draft created: ${article.url}/edit`)
    }
})()
Enter fullscreen mode Exit fullscreen mode

Function to prepare data to post to the API:

./scripts/publish-to-devto.js

...

function getPayload(file) {
    return {
        article: {
            title: file.data.title.trim(),
            // replace relative paths with absolute URLs
            body_markdown: file.content.trim().replace(/\]\(\/posts\//gm, `](${siteUrl}/posts/`),
            published: autoPublish,
            // if you want blog post to be a part of Series on DEV
            series: undefined,
            // cover image URL
            main_image: file.data.image ? `${siteUrl}${file.data.image}` : undefined,
            // generate the canonical url (file name minus .md in my case)
            canonical_url: `${siteUrl}/${file.path.split('/').slice(-2, -1)[0]}`,
            description: file.data.description,
            // DEV allows only 4 tags and they must be alphanumeric
            tags: file.data.tags.slice(0, 4).map(tag => tag.toLowerCase().replace(/[^a-z0-9]/i, '')),
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

And finally a function to publish the prepared payload to the API:

./scripts/publish-to-devto.js

...

async function publish(payload) {
    const response = await fetch(apiUrl, {
        method: 'post',
        body: JSON.stringify(payload),
        headers: {'Content-Type': 'application/json', 'api-key': apiKey}
    });

    const json = await response.json();
    if (json.error) {
        throw new Error(`API returned an error: ${json.error}`)
    }

    return json
}
Enter fullscreen mode Exit fullscreen mode

Add a new script to the package.json:

./package.json

{
  ...
  "scripts": {
    ...
    "publish-to-devto": "node ./scripts/publish-to-devto.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

And call it from the command line:

npm run publish-to-devto posts/2021/01/add-docker-container-name-to-shell-prompt.md
Enter fullscreen mode Exit fullscreen mode

...

Alright! We just got rid of some boring manual work which is always good.

Top comments (0)