How To Add Monaco Editor to a Next.js app
Tags: nextjs, monaco, javascript
Published: 2020-03-30 23:29:25

## Bottom Line Up Front

I use a slightly modified version of the steps mentioned in [this GitHub comment](https://github.com/react-monaco-editor/react-monaco-editor/issues/271#issuecomment-601835902). Modifications were necessary because I use TailwindCSS with Next.js.

- [YouTube recording (18 mins)](https://youtu.be/13UVFrGe80o)
- Dev.to Embed:

{% youtube 13UVFrGe80o %}

## Motivations

Monaco Editor is [the open source editor]
(https://github.com/microsoft/monaco-editor) used in [VS Code](https://github.com/microsoft/vscode), which itself is open source. I used to write my blogposts in VS Code, and as I [make my own Dev.to CMS](https://dev.to/swyx/make-your-own-dev-to-cms-livestream-part-1-2ad1), 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.
- 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](https://github.com/zeit/next.js/issues/10975) 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`](https://www.npmjs.com/package/@zeit/next-css) and [`next-transpile-modules`](https://www.npmjs.com/package/next-transpile-modules)

We can solve this with [a solution worked out by Elliot Hesp on GitHub](https://github.com/react-monaco-editor/react-monaco-editor/issues/271#issuecomment-601835902) and [a config from Joe Haddad of the Next.js team](https://gist.github.com/Timer/2ed585ff0db6b885a7f7dd738a982b13).

## 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.

```js
// next.config.js

const 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:

```tsx
import React from "react";
// etc

import 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>)
}
```

Since I'm using Tailwind, I'm also using PostCSS, which also [tries to eliminate Monaco's CSS](https://dev.to/jaakkolantero/comment/n7hl). You have to tell it to ignore that:

```js
// postcss.config.js
const purgecss = [
  "@fullhuman/postcss-purgecss",
  {
    // https://purgecss.com/configuration.html#options
    content: ["./components/**/*.tsx", "./pages/**/*.tsx"],
    css: [],
    whitelistPatternsChildren: [/monaco-editor/], // so it handles .monaco-editor .foo .bar
    defaultExtractor: content => content.match(/[\w-/.:]+(?<!:)/g) || []
  }
];
```


## Catch up on the Dev.to CMS LiveStream!

- [Day 1 - Setup Next.js and Tailwind UI, list posts through API routes](https://dev.to/swyx/make-your-own-dev-to-cms-livestream-part-1-2ad1) - 90 mins
- [Day 2 - setting up a Markdown Editor with Next.js, Tailwind UI, Highlight.js, React Hook Form, and React Query](https://dev.to/swyx/make-your-own-dev-to-cms-livestream-part-2-6mf) - 3 hours
- Quick Fix - [How To Add Monaco Editor to a Next.js app](https://dev.to/swyx/how-to-add-monaco-editor-to-a-next-js-app-ha3) - 18 mins
- [Day 3 - Refactoring to Edit Existing Posts](https://dev.to/swyx/make-your-own-dev-to-cms-livestream-part-3-c65) - 3 hours
- [Day 4 - Polish Day! Implementing Notifications, Markdown preview, and programmatic Redirects, and Using Web Components in Next.js](https://dev.to/swyx/make-your-own-dev-to-cms-livestream-part-4-6em) - 3 hours
- [Instant GraphQL with OneGraph - Screenshares in Public with Sean Grove](https://dev.to/swyx/instant-graphql-with-onegraph-4hi8) - refactoring handrolled Dev.to API access with OneGraph and GraphQL
- [How and Why to Un-Reset Tailwind's CSS Reset](https://dev.to/swyx/how-and-why-to-un-reset-tailwind-s-css-reset-46c5)