挨拶
55代プログラミング研究会 わせりんです。Web系イキれたら楽しいだろうなと思っています。
Next.jsとEditor.jsでブロックエディタを作る時に難しかったことが色々あったので、後継の役に立てばと思ってこの記事を生成しました。
対象者
Notionに惚れ込んでたり、HackMDが大好きな人 兼 自作してみたいという人。
この記事を読めば多分1時間くらいでそれっぽいエディタがnext.jsで作れるようになります。
完成図
こんな感じのイケてるエディタが出来上がる予定です。かっこいいですね。

公式のドキュメントはこちら。英語読めちゃうよって人はこっちのほうが早いです。 Editor.js
お断り
今回はwindows10, node version 14.18.1です。それ以外のバージョンはうまくいかないかもしれません。環境によっては動かない可能性があります。
また、解説の内容は一部厳密性を欠いていることがあります。ご留意ください。
Next.jsとは?
この記事を読む方はおそらく知っていますが、少しだけ解説します。
webアプリを作るフレームワークの一つで、Reactの拡張的な存在です。サーバー機能を持っていることもあり、フロント側だけで気楽にwebアプリが作れることもあります。
ポケモン最新作のホームページはNext.jsを使っていたりします。
Next.jsの特徴は主に3つです。 1. 初期データを埋め込んだHTMLをブラウザに送信するので、無駄にJSファイルを実行せずにすみます。つまり、読み込み時間が早いです。 2. ファイルの配置によってURLルーティングができます。 3. 設定がある程度自動でやってくれます。 つまり、描画が早くて開発が簡単になります。とっても便利ですね。 しかし、Next.jsのサーバーサイドで構築するという機能には問題もあります。これが今回のメインポイントなので後ほど解説します。
1について細かく知りたい人はこのサイトがとても良いでしょう。 Server Side Renderingについて知るべきこと。Server Side Renderingとは何か? それによって何が改善されるのか?(前編) ng-japan 2017 - Publickey
Editor.jsとは?
Notionっぽいブロックエディタを簡単に使えるJavaScriptのライブラリです。 出力がJSON形式で汎用なデータが取れます。 カスタマイズ性が高く、自分好みのエディタが使えます。 大まかなイメージは完成図を参照してみてください。
詳細はこちらから。
シンプルで使ってみたいWYSIWYGエディター Editor.js | アールエフェクト
作り方
next jsの環境構築
コマンドラインツールから任意フォルダ上で
create-next-appコマンドを実行するとnext.jsのプロジェクトフォルダが作成されます。
※next-appには任意のプロジェクト名を設定します。
npx create-next-app next-app
作成したフォルダ内で npm run dev コマンドを実行するとローカル上でアプリが起動します。
npm run dev
これで、このページが確認できれば一旦完成です。

詳細はこちらで確認してください。
editor jsの環境構築
Editor.jsをインストールします。
npm i @editorjs/editorjs — save
これでひとまずEditor.jsは使えます。 次に初期設定をします。 Editorの設定を行うファイルとEditorを表示するindex.jsファイルに分けて作ります。使うファイルの構成は以下のとおりです。
/components - Editor.js - tool.js /pages - index.js
next.jsがメインで表示するIndex.jsは以下のコードで実装します。 ここでは主に、Next.jsの特徴であるSSRを無効にしつつ、Editorを埋め込んで居ます。
import dynamic from "next/dynamic";
const CustomEditor = dynamic(() => import("../components/Editor.js"), {
ssr: false,
});
const Home = () => {
const [data, setData] = useState();
return (
<>
<h1>Editor</h1>
<CustomEditor
data={data}
onChange={setData}
holder="editorjs-container"
/>
</>
);
};
export default Home;
このコードでは、ブロックエディタ画面をサーバーサイドでのレンダリング後に読みこむためにdynamicメソッドを利用しています。ssrは falseにしないとバグります。
Indexで表示するエディター画面は、下記のコードで作ります。 ここでは、hooksをつかって変更内容を保存し、index.jsにわたす内容を作成しています。
import React, { memo, useEffect, useRef } from "react";
import EditorJS from "@editorjs/editorjs";
import TOOLS from "./tools";
const CustomEditor = ({ data, onChange, holder }) => {
//initialize editorjs
const ref = useRef();
useEffect(() => {
//initialize editor if we don't have a reference
if (!ref.current) {
const editor = new EditorJS({
holder: holder,
tools: TOOLS,
autofocus: true,
data,
placeholder: "Start writing your story...",
async onChange(api) {
const data = await api.saver.save();
onChange(data);
},
});
ref.current = editor;
}
//add a return function handle cleanup
return () => {
if (ref.current && ref.current.destroy) {
ref.current.destroy();
}
};
}, []);
return (
<>
<div id={holder}></div>
</>
);
};
// 状態の変更をこのFCの変更によってのみ検知する。
export default memo(CustomEditor);
const ref = useRef();:
useRefを使用して初期化されます。このhookは、カスタムエディターの参照を保持するために使用されます。
useEffect(() => {...}, []);:
この行では、 useEffectを使用して、カスタムエディターを初期化するための関数を定義します。初回のレンダリング時のみ実行しています。
!ref.currentで refの中身が無い時にインスタンス生成するようにしています。
const editor = new EditorJS({...});: この行では、 EditorJSクラスの新しいインスタンスを作成します。このインスタンスには、次の引数が渡されます。
holder: この引数は、カスタムエディターが表示されるHTML要素のIDを指定します。
tools: この引数は、EditorJSに対応するカスタムツールを定義したJavaScriptファイルを指定します。 autofocus: この引数は、エディターを自動的にフォーカスするかどうかを指定します。
data: この引数は、エディターに表示する初期データを指定します。
placeholder: この引数は、エディターが空の場合に表示するプレースホルダー文字列を指定します。 async onChange(api) {...}: この引数は、エディターの内容が変更されたときに呼び出される非同期関数を指定します。この関数では、 api.saver.saveメソッドを使用して、エディターの内容を保存し、 onChange関数を呼び出して、変更を外部に通知します。
return () => {
if (ref.current && ref.current.destroy) {
ref.current.destroy();
}
ここでは、すでに定義されているときに、2つ以上エディターを作らないようにしています。
最後に、
<>
<div id={holder}></div>
</>
この部分がindex.jsで表示されるようになります。
Editorのプラグインをtools.jsにまとめて記載しています。これらのツールについては、全て
npm i --save @editorjs/プラグイン名
でインストールしてから下記のように記載してみてください。
tool.js
import Header from "@editorjs/header";
import CheckList from "@editorjs/checklist";
import code from "@editorjs/code";
import Delimiter from "@editorjs/delimiter";
import Embed from "@editorjs/embed";
import InlineCode from "@editorjs/inline-code";
import LinkTool from "@editorjs/link";
import List from "@editorjs/list";
import Quote from "@editorjs/quote";
import { Paragraph } from "@editorjs/paragraph";
const TOOLS = {
header: {
class: Header,
},
},
checklist: CheckList,
code: code,
delimiter: Delimiter,
embed: Embed,
inlineCode: InlineCode,
linkTool: LinkTool,
list: List,
quote: Quote,
};
export default TOOLS;
toolsについては、書き方が固定されているので、このまま使ってください。基本的には、importしたプラグインの内容をTOOLS以下で呼び出しています。
Ultimate Guide on how to setup Editor.js with Next.js 12+ (Typescript) | ReactHustle
Next.js & Editor.js Complete Setup Guide | by Fazley Rabbi | Medium
注意点
- ssrをfalseにすること
- 一回初期化したら二回目以降を止める(useRefで状態が出来上がっているかどうかを検知) この点抜けていると、バグがおきるので気をつけてください。
拡張方法
前述したとおり、プラグインをtools.jsに追加すれば出来ます。
また、自分でプラグインを作成することも可能です。ここでは細かく言及しませんが、documentにて詳細な情報を載せています。その他には、このサイトを参照してみてください。
Editor.jsを使ってテキストエディタを作る - Qiita
まとめ
ここまでで、ブロックエディターをNext.js環境下で作成する方法をまとめました。色々欠けている点はありますが、所々で添付した参考サイトと合わせれば、自分だけの完璧なエディターを作れるようになります。
皆様のイチオシエディターを待っています
参考文献
神ドキュメント
次回予告
明日は56代のなださんによる、「初心者向け顔の描き方講座」です。お楽しみに!