Writing a blog should be a seamless and enjoyable process. However, I found myself constantly frustrated with the high-friction workflow involved in adding images to my MD(X) blog files. The process typically involved several tedious steps:
public/static
directory./static/imagename
.This process was not only time-consuming but also detracted from the pleasure of writing articles. So I went looking for CMS solutions, which I didn't know much about but felt promising.
I explored several CMS solutions, including:
While these platforms offered a lot of functionality, they introduced new challenges. For instance, you often need to pay for cloud hosting or manage self-hosting, which involves dealing with backups for both content (usually in a database) and media files (images and videos). Additionally, integrating the CMS content into a Next.js app requires making API calls, which adds complexity to the build process and necessitates environment variable configuration and deployment triggers.
Ghost provided an excellent WYSIWYG editor and robust metadata management, but it had significant drawbacks for my use case:
Ultimately, I missed the simplicity of having content and assets colocated with my app. A straightforward GitHub repository setup, without the need for complex environment variables, API calls or backups felt better suited to my needs.
Fed up with the manual steps involved in image management, I decided to automate the process. My goal was to create a simple script that would:
npm run <myscript>
.I created a scripts/pasteimg.ts
file and began writing the script, leveraging GPT to expedite the process. One challenge was getting GPT to handle "taking the image from the clipboard," as it initially suggested using clipboardy
, which isn't compatible with images.
Fortunately, I discovered the npm package save-clipboard-image, which uses OSX native commands and perfectly fit my needs.
After spending a few minutes troubleshooting errors related to ESM modules while trying to run the script with ts-node
, a recommendation to use tsx resolved the issue instantly.
Here's a demonstration of the script in action:
And the code behind it:
Note: Only works on MacOS, you'll need to adjust the script for Windows or Linux.
import { saveClipboardImage } from "save-clipboard-image";
import inquirer from "inquirer";
import * as path from "path";
import * as fs from "fs";
import clipboardy from "clipboardy";
const NEXT_STATIC_DIR = "/static";
const NEXT_STATIC_PATH = path.join(process.cwd(), "/public", NEXT_STATIC_DIR);
async function run() {
// Ask for the image name
const { imageName } = await inquirer.prompt({
name: "imageName",
type: "input",
message: "Enter the name of the image (without extension):",
validate: (input: string) =>
input.trim() !== "" ? true : "Image name cannot be empty",
});
// Ask for the path with a default value
const { subdir } = await inquirer.prompt({
name: "subdir",
type: "input",
message: "Enter the target directory:",
default: "",
});
const fullTargetDirectory = path.join(NEXT_STATIC_PATH, subdir);
// Ensure the directory exists
if (!fs.existsSync(fullTargetDirectory)) {
fs.mkdirSync(fullTargetDirectory, { recursive: true });
}
// Save the image from the clipboard
try {
await saveClipboardImage(fullTargetDirectory, imageName);
} catch (error) {
// No image in clipboard, explain and exit
console.error("No image found in clipboard, please copy an image first.");
return;
}
// Construct the full path
const fullImagePath = path.join(fullTargetDirectory, `${imageName}.png`);
const relativeImagePath = path.join(NEXT_STATIC_DIR, imageName + ".png");
const jsxCode = `
<Image
src="${relativeImagePath}"
alt="${imageName}"
width={500}
height={200}
className="text-center shadow rounded-md"
/>`;
// Copy the generated JSX code to the clipboard using clipboardy
clipboardy.writeSync(jsxCode);
// Log success in terminal + summary (image path and JSX code)
console.log("Image saved successfully!");
console.log(`Image path: ${fullImagePath}`);
console.log(`JSX code copied to clipboard: ${jsxCode}`);
}
run();
I'm thrilled with this custom script. It significantly simplifies the process of adding images to my blog, allowing me to focus on writing. Next.js takes care of optimizing the images for various screen sizes, and by committing both the content and media to the same repository, I no longer worry about backups or synchronization issues.