Published on
Last Updated on
Estimated Reading Time: 4 min
MDX and gatsby are great for building a blog. But it's a pain to create all the skeleton stuff every time I want to create a new blog post.
Steps to create a new blog post
- Create a new folder under content/posts///
- Create a new index.mdx file in the above folder.
- Copy the frontmatter from a template file or an existing post.
- Modify most of the properties for the new post.
- If the post has an image gallery, then
- create a subfolder with the name images.
- copy a data.json file from an existing folder and update it.
Here is an example of the frontmatter for this post
---
author: 'Ankur'
date: '2019-09-12'
slug: 'automating-creation-of-new-blog-post'
title: 'Automating the creation of a new blog post'
excerpt: 'Creating a new blogpost in markdown and gatsby has quite a bit of skeleton stuff. Lets see how to automate it.'
tags:
- 'tutorial'
- 'gatsby'
- 'typescript'
featuredImage: ''
featuredImagePosition: ''
---
Wouldn't it be nice if it could all be generated by a single command?
Now you might ask whether it's worth spending the few extra hours to automate something that takes just a few minutes of my time.
You might be correct in saying that it doesn't save me much time, and there is not much of a context switch when I am starting a new post. But it's a pain creating this manually every time, and it's also error-prone.
Let's Automate all the things
Libraries we will be using
Install the following packages using yarn or npm.
- slugify: Slugify the title automatically
- inquirer: CLI get user input for title, excerpt, tags etc
- jsToYaml: Convert json to YAML
- mkdirp: helper to create directories
- prettier: Optional. Code formatter to format the generated files using a prettier config.
Let's build the script
Helper functions
const padLeft0 = (n: number) => n.toString().padStart(2, '0');
const fromRoot = (...p: string[]) => path.join(__dirname, '..', ...p);
const formatDate = (d: Date) => `${d.getFullYear()}-${padLeft0(d.getMonth() + 1)}-${padLeft0(d.getDate())}`;
const listify = (a: string) => (a && a.trim().length ? a.split(',').map((s) => s.trim()) : '');
Use Inquirer to get details from the user
In my case, I am getting the title, description, tags and whether the post has images.
const prompt = await inquirer.prompt([
{
type: 'input',
name: 'title',
message: 'Title',
},
{
type: 'input',
name: 'description',
message: 'Excerpt/Description',
},
{
type: 'input',
name: 'tags',
message: 'Tags/Keywords (comma separated)',
},
{
type: 'list',
name: 'images',
message: 'Post has image gallery (yes/no)',
choices: [
{ name: 'Yes', value: 'Y' },
{ name: 'No', value: 'N' },
],
},
]);
const { title, description, tags, images } = prompt;
Create the folder structure
const date = new Date();
const slug = slugify(title);
const destination = fromRoot('content/posts', `${date.getFullYear().toString()}`, `${formatDate(date)}-${slug}`);
mkdirp.sync(destination);
The date is set to the current date, and the slug is automatically created from the title.
The folder is created with a consistent structure.
Create the MDX file with the frontmatter
const yaml = jsToYaml.stringify({
author: 'Ankur Sheel',
date: formatDate(new Date()),
slug,
title,
excerpt: description,
tags: listify(tags),
featuredImage: '',
featuredImagePosition: '',
imageFacebook: './image-facebook.png',
imageTwitter: './image-twitter.png',
});
const markdown = prettier.format(`---\r\n${yaml}\r\n---\r\n`, {
...require('../.prettierrc'), // eslint-disable-line global-require
trailingComma: 'es5',
endOfLine: 'crlf',
parser: 'mdx',
});
fs.writeFileSync(path.join(destination, 'index.mdx'), markdown);
So what's happening in the above code snippet
- Lines 1-12: Create a JSON object and use jsToYaml to convert it to a YAML object.
- Lines 13-18: Use prettier to get a formatted string of the yaml object and wrap it with --- so that it's in a format that the MDX expects.
Some of the properties are hardcoded, such as author, imageFacebook, imageTwitter, and the rest are auto-generated.
The file name is also consistently named index.mdx and lives in the created folder.
Create the images folder and data.json (if required)
if (images === 'Y') {
const data = {
gallery: [{ image: `./${slug}-1.jpg`, title: '' }],
};
const json = prettier.format(JSON.stringify(data, null, 4), {
...require('../.prettierrc'), // eslint-disable-line global-require
trailingComma: 'es5',
endOfLine: 'crlf',
parser: 'json',
});
const imagesDestination = path.join(destination, 'images');
mkdirp.sync(imagesDestination);
fs.writeFileSync(path.join(imagesDestination, 'data.json'), json);
}
Let's see what's happening here.
- Line 1: Create the images folder only if the user indicated that this post will have an image gallery.
- Lines 2-4: Create the basic JSON object with a single entry for image.
- Lines 6-11: Use prettier to get a formatted string of the json object.
- Lines 13-14: Create the images folder.
- Line 16: Create the data.json file.
Other Notes
- It might be better/faster to write the generator using plop so that I have a single template file that can be used. Plop also supports modifying a file which might be helpful if I have many blog posts and want to change the frontmatter structure.
Conclusion
Setting up a new blog post can take a few minutes of my time and can be prone to errors, but with this script, I just need to call the script, enter a few values specific to the post, and I am ready to start writing.
You can see the whole script here.
Have you automated something in your projects? What modifications would you make to this script? Let me know.