When I started working on my chess side-blog project I didn't want to feel the pressure of creating blog cover images for each - so I started scrolling through the Gridsome Plugins and found Edmund1645's blog-cover plugin! Unfortunately for me, after going through the setup process I was receiving an error message.
After digging through the Plugin's and Gridsome's code for an hour or two, I found that the issue seemed to be occurring when Vue-Remark is used in conjunction with the collection.updateNode()
function.
I wasn't entirely set on using Cloudinary to host my images, so I decided to modify the code so that I didn't need to programmatically update the frontmatter of my articles, but rather could generate the images into a /covers/
folder with the same name as the article, thus achieving my goal of saving time with creating blog cover images!
npm installs
If you aren't familiar with npm, check out my npm post.
If you are, run the following:
npm install fs-extra node-html-to-image
# I'm going to reference vue-remark later,
# if you want to use that you'll also need:
npm install @gridsome/vue-remark
Vue-Remark plugin config
The important part of the configuration for generating blog cover images is the typeName
- for more details on configuring vue-remark see the plugin's page.
module.exports = {
siteName: "Your Site Name Here",
plugins: [
{
use: "@gridsome/vue-remark",
options: {
typeName: "Post", // < Note this value! 📝
baseDir: "./content/posts",
pathPrefix: "/post",
template: "./src/templates/Post.vue",
},
},
],
};
You'll also want to make sure you have at least one markdown file created in the ./content/posts/
directory with the bare minimum frontmatter:
// intro.md
---
title: Intro Post
cover_image: "../covers/intro.png"
---
This is a sample post!
If you're trying to follow along, make sure to create a Post.vue
file in your templates folder so that gridsome develop
can create your post pages properly!
HTML to an image
So now we've got our sample post ready to go - let's setup our html to be converted to an image!
In the root directory, create a functions
folder with a generateHtml.js
file inside. The HTML returned by this code is going to be what becomes our post cover image.
// This creates a basic solid background with title and a white border
module.exports = function(
title,
{ backgroundColors, imgHeight, imgWidth, border, domain }
) {
const bgColor =
backgroundColors[Math.floor(Math.random() * backgroundColors.length)];
const template = `
<html>
<head>
<style>
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
body {
width: ${imgWidth};
height: ${imgHeight};
padding: 40px;
background: ${bgColor};
color: #ffffff;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
div.container {
border: 3px solid #ffffff;
text-align: left;
padding: 30px;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-around;
}
h1.title {
font-size: 3.7rem;
text-transform: capitalize;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<h1 class="title">${title}</h1>
</div>
</body>
</html>
`;
return template;
};
gridsome.server Setup
Finally, our gridsome.server
setup - this is where we will check each time gridsome develop
or gridsome build
is run to see if any posts are missing a cover image. If they are, the image will be generated in the covers folder.
const fs = require("fs-extra");
const createImage = require("node-html-to-image");
const generateHtml = require("./functions/generateHtml");
const defaultOptions = {
typeName: "Post", // This should be the typeName noted above 📝
// 👇🏻 Each background will randomly have one of these colors
backgroundColors: [
"#23313B",
"#636655",
"#607077",
"#806752",
"#5d6f75",
"#915335",
],
imgWidth: "1024px", // The width of your cover image
imgHeight: "512px", // The height of your cover image
border: true, // I hard coded this to true in my html
domain: "Your domain goes here", // Edmund includes this in their generated images
outputDir: "content/covers/", // Where the cover images should be generated to
};
module.exports = function(api) {
// Keeping this for easy modifications from gridsome-plugin-blog-cover
const options = { ...defaultOptions };
api.loadSource(async (actions) => {
const collection = actions.getCollection(options.typeName);
collection.data().forEach(function(node) {
if (node.internal.typeName === options.typeName) {
// Using the same filename as the file for easy frontmatter
const imgName = node.fileInfo.name;
fs.ensureDirSync(options.outputDir);
const output = `${options.outputDir}/${imgName}.png`;
// Only generate images for files that don't exist already
console.log("Generating Missing Cover Images");
fs.access(output, (error) => {
if (error) {
console.log(`Creating ${imgName}.png`);
createImage({
output,
html: generateHtml(node.title, node.subtitle, options),
});
} else {
console.log(`${output} already exists`);
}
});
// if updateNode() worked, this is where it would go :)
}
});
});
};
Thanks again to Edmund1645 for the baseline work for this!
Top comments (2)
This is a very cool project! So many use cases with the node-html-to-image package. Great job!
It's been a lot of fun tinkering with!
I have a feeling I'll be using the package again in the future 🥳