How To Add Monaco Editor to a Next.js app
Bottom Line Up Front
I use a slightly modified version of the steps mentioned in this GitHub comment. Modifications were necessary because I use TailwindCSS with Next.js.
- YouTube recording (18 mins)
- Dev.to Embed:
Motivations
Monaco Editor is the open source editor used in VS Code, which itself is open source. I used to write my blogposts in VS Code, and as I make my own Dev.to CMS, I wanted to have all the familiar trappings of Monaco to help me out while I write.
Problems
However there are some issues we have to deal with:
- Monaco is framework agnostic, so it requires writing some React bindings.
- You could do it yourself, but also you could just skip that and use https://github.com/react-monaco-editor/react-monaco-editor
- Monaco is written for a desktop Electron app, not for a server-side rendered web app.
- This is solved by using
import dynamic from "next/dynamic"
and making Monaco a dynamic import.
- This is solved by using
- Monaco also wants to offload syntax highlighting to web workers, and we need to figure that out
- Next.js doesn't want any dependencies importing CSS from within
node_modules
, as this assumes a bundler and loader setup (e.g. webpack) and can have unintentional global CSS side effects (all global CSS is intended to be in_app.js
).- we can re-enable this with
@zeit/next-css
andnext-transpile-modules
- we can re-enable this with
We can solve this with a solution worked out by Elliot Hesp on GitHub and a config from Joe Haddad of the Next.js team.
Solution
The solution I use is informed by my usage of Tailwind CSS, which requires a recent version of PostCSS, which @zeit/next-css
only has at 3.0 (because it is deprecated and not maintained).
I also use TypeScript, which introduces a small wrinkle, because Monaco Editor attaches a MonacoEnvironment
global on the window
object - I just @ts-ignore
it.
// next.config.jsconst MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");const withTM = require("next-transpile-modules")([ // `monaco-editor` isn't published to npm correctly: it includes both CSS // imports and non-Node friendly syntax, so it needs to be compiled. "monaco-editor"]);module.exports = withTM({ webpack: config => { const rule = config.module.rules .find(rule => rule.oneOf) .oneOf.find( r => // Find the global CSS loader r.issuer && r.issuer.include && r.issuer.include.includes("_app") ); if (rule) { rule.issuer.include = [ rule.issuer.include, // Allow `monaco-editor` to import global CSS: /[\\/]node_modules[\\/]monaco-editor[\\/]/ ]; } config.plugins.push( new MonacoWebpackPlugin({ languages: [ "json", "markdown", "css", "typescript", "javascript", "html", "graphql", "python", "scss", "yaml" ], filename: "static/[name].worker.js" }) ); return config; }});
and then in your Next.js app code:
import React from "react";// etcimport dynamic from "next/dynamic";const MonacoEditor = dynamic(import("react-monaco-editor"), { ssr: false });function App() { const [postBody, setPostBody] = React.useState(""); // etc return (<div> {/* etc */} <MonacoEditor editorDidMount={() => { // @ts-ignore window.MonacoEnvironment.getWorkerUrl = ( _moduleId: string, label: string ) => { if (label === "json") return "_next/static/json.worker.js"; if (label === "css") return "_next/static/css.worker.js"; if (label === "html") return "_next/static/html.worker.js"; if ( label === "typescript" || label === "javascript" ) return "_next/static/ts.worker.js"; return "_next/static/editor.worker.js"; }; }} width="800" height="600" language="markdown" theme="vs-dark" value={postBody} options={{ minimap: { enabled: false } }} onChange={setPostBody} /> </div>)}
Links
Original Link: https://dev.to/swyx/how-to-add-monaco-editor-to-a-next-js-app-ha3
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To