
In this article, I’m gonna show you how you can set up Editor.js In your Next.js project. I’ve faced some problems adding Editor.js with Next.js. With the help of Stack Overflow & GitHub, I’ve figured out how to implement it. This is gonna be my complete guide of Editor.js with Next.js.
For those who don’t know about Editor.js, It’s block styled editor. You can read about it here.

Prerequisites:
- Basic Understanding of React.js & Next.js
Step 1: Install Editor.js & plugins
Install the package via NPM or Yarn
Step 2: Configure & Tools Installation
Each block of Editor.js is provided by a Plugin. By default, Editor.js comes with only a paragraph block. Probably you want to use several Block Tools that should be installed and connected.
In this project, we need these plugins, head over to the GitHub page, and install the plugins.
1. Header
2. Checklist
3. Code
4. Delimiter
5. Embed
6. Image
7. Inline-Code
8. Link
9. List
10. Quote
11. Simple-Image
You can find other plugins here.
Okay, now that everything sets up. Let’s do some coding…
Step 3: Custom Editor Component
Create a component name CustomEditor & Paste these codes. In this component, we are gonna configure Editor.js & plugins. Here we are receiving three props data, imageArray & handleInstance. So what are they doing?
data -This will show the saved data in Editor.js.
imageArray -After uploading the image to the server we will get the URL and pass it in this props. This will keep track of the total uploaded images. When we remove an image from the editor it should remove from the server too right. This array will make sure we delete unused images. We will later see how this works.
handleInstance -This is an instance of Editor.js with that we can get data from the editor and save it.
import EditorJs from "react-editor-js";
import CheckList from "@editorjs/checklist";
import CodeBox from "@bomdi/codebox";
import Delimiter from "@editorjs/delimiter";
import Embed from "@editorjs/embed";
import Image from "@editorjs/image";
import InlineCode from "@editorjs/inline-code";
import LinkTool from "@editorjs/link";
import List from "@editorjs/list";
import Quote from "@editorjs/quote";
import SimpleImage from "@editorjs/simple-image";
import Header from "@editorjs/header"
/*Your server url*/
import API from "../api/image"
const CustomEditor = ({data, imageArray, handleInstance}) => {
const EDITOR_JS_TOOLS = {
embed: Embed,
header: Header,
list: List,
codeBox: CodeBox,
linkTool: LinkTool,
image: {
class: Image,
config: {
uploader: {
uploadByFile(file) {
let formData = new FormData();
formData.append("images", file);
/*send image to server*/
return API.imageUpload(formData).then((res) => {
/*get the uploaded image path, pushing image path to image array*/
imageArray.push(res.data.data)
return {
success: 1,
file: {
url: res.data.data
}
}
})
}
}
}
},
quote: Quote,
checklist: CheckList,
delimiter: Delimiter,
inlineCode: InlineCode,
simpleImage: SimpleImage
}
/* Editor.js This will show block editor in component*/
/*pass EDITOR_JS_TOOLS in tools props to configure tools with editor.js*/
return <EditorJs instanceRef={(instance) => handleInstance(instance)}
tools={EDITOR_JS_TOOLS} data={data}
placeholder={`Write from here...`}/>
}
/*Return the CustomEditor to use by other components.*/
export default CustomEditor
Step 4: Use CustomEditor to create a new article
Okay, now create a js file named New.js. Here we will show our editor. We have to import the CustomEditor dynamically. And make sure you added ssr:false. Otherwise, you will face many errors and issues.
import dynamic from "next/dynamic";
let CustomEditor = dynamic(() => import('../../../components/CustomEditor'), {
ssr: false
});
import React, {Fragment, useState} from 'react';
import dynamic from "next/dynamic";
import {connect} from "react-redux";
import {createArticle} from "../../../redux/actions/article";
let CustomEditor = dynamic(() => import('../../../components/CustomEditor'), {
ssr: false
});
const New = () => {
const [imageArray, setImageArray] = useState([]) /* to keep track of uploaded image */
let [editorInstance, setEditorInstance] = useState({}) /* to get the instance of editor.Js */
/* remove image from imageArray */
function removeImage(img) {
const array = imageArray.filter(image => image !== img)
setImageArray(array)
}
const handleInstance = (instance) => {
setEditorInstance(instance)
}
const saveArticle = async (e) => {
e.preventDefault()
/* get the editor.js content and save it to server */
const savedData = await editorInstance.save();
const data = {
description: JSON.stringify(savedData),
}
/* Clear all the unused images from server */
await clearEditorLeftoverImages()
/* Save article to server */
createArticle(data, files)
}
/* This method will get the current images that are used by editor js,
and images that stored in imageArray. It will compare and call server request to
remove unused image */
const clearEditorLeftoverImages = async () => {
/* Get editorJs images */
const currentImages = []
document.querySelectorAll('.image-tool__image-picture')
.forEach((x) => currentImages.push(x.src.includes("/images/") && x.src))
if (imageArray.length > currentImages.length) {
/* image deleted */
for (const img of imageArray) {
if (!currentImages.includes(img)) {
try {
/* delete image from backend */
await API.deleteImage({imagePath: img})
/* remove from array */
removeImage(img)
} catch (err) {
console.log(err.message)
}
}
}
}
}
return (
<Fragment>
<button onClick={saveArticle}>Save</button>
{CustomEditor && <CustomEditor handleInstance={handleInstance} imageArray={imageArray}/>}
</Fragment>
);
}
export default connect(state => state, {createArticle})(New);
Step 5: Update saved article
It’s gonna be like new.js with some changes. Here we are fetching the previously saved editor.js from the server. We will loop the path of the saved image from editorData content and push it in imageArray. This time we will pass data props in CustomEditor to show our saved data in editor.
import React, {Fragment, useEffect, useState} from 'react';
import dynamic from "next/dynamic";
import {connect} from "react-redux";
import {router} from "next/client";
let CustomEditor = dynamic(() => import('../../../components/CustomEditor'), {
ssr: false
});
const Update = () => {
const [imageArray, setImageArray] = useState([]) /* to keep track of uploaded image */
let [editorInstance, setEditorInstance] = useState({}) /* to get the instance of editor.Js */
const [editorData, setData] = useState(null) /* to store editorjs data from server or other source and show it in editor.js */
useEffect(async () => {
const {id} = router.query
if (id) {
/* Get article from server or other source */
await getArticleDetails(id)
}
}, [])
const getArticleDetails = async (id) => {
const res = await ArticleAPI.getArticle(id)
const resData = res.data.data
/* parse string json to JSON */
const editorData = JSON.parse(resData.description)
for (const block of editorData.blocks) {
if (block.type === 'image') {
/* Get the image path and save it in image array for later comparison */
addImages(block.data.file.url)
}
}
setData(editorData)
}
/* add image to imageArray */
function addImages(img) {
imageArray.push(img)
}
/* remove image from imageArray */
function removeImage(img) {
const array = imageArray.filter(image => image !== img)
setImageArray(array)
}
const handleInstance = (instance) => {
setEditorInstance(instance)
}
const saveArticle = async (e) => {
e.preventDefault()
/* get the editor.js content and save it to server */
const savedData = await editorInstance.save();
const data = {
description: JSON.stringify(savedData),
}
/* Clear all the unused images from server */
await clearEditorLeftoverImages()
/* Save article to server */
createArticle(data)
}
/* This method will get the current images that are used by editor js,
and images that stored in imageArray. It will compare and call server request to
remove unused image */
const clearEditorLeftoverImages = async () => {
/* Get editorJs images */
const currentImages = []
document.querySelectorAll('.image-tool__image-picture')
.forEach((x) => currentImages.push(x.src.includes("/images/") && x.src))
if (imageArray.length > currentImages.length) {
/* image deleted */
for (const img of imageArray) {
if (!currentImages.includes(img)) {
try {
/* delete image from backend */
await API.deleteImage({imagePath: img})
/* remove from array */
removeImage(img)
} catch (err) {
console.log(err.message)
}
}
}
}
}
return (
<Fragment>
<button onClick={saveArticle}>Save</button>
{CustomEditor && <CustomEditor handleInstance={handleInstance}
data={editorData} /* here we will pass the saved editorjs data to show in the editor */
imageArray={imageArray}/>}
</Fragment>
);
}
export default connect(state => state)(Update);
Thanks for reading this article. I know it’s a little complicated. It will be helpful for those who face some problem adding Editor.js with Next.js. If you have any questions regarding it you can ask me. I hope this article will help you to add Editor.js with Next.js.
After following this blog, if you still having some issue with setting up Editorjs, check this gist here or contact me.