{"id":21228939,"url":"https://github.com/anthonycorletti/partykit-nextjs-monaco-editor-example","last_synced_at":"2025-09-05T21:37:25.731Z","repository":{"id":227741228,"uuid":"772285770","full_name":"anthonycorletti/partykit-nextjs-monaco-editor-example","owner":"anthonycorletti","description":null,"archived":false,"fork":false,"pushed_at":"2024-03-14T23:01:31.000Z","size":104,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-27T13:51:17.870Z","etag":null,"topics":["bun","monaco-editor-react","nextjs","partykit","typescript"],"latest_commit_sha":null,"homepage":"https://partykit-nextjs-monaco-editor-example.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/anthonycorletti.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-14T22:24:19.000Z","updated_at":"2025-01-31T09:06:48.000Z","dependencies_parsed_at":null,"dependency_job_id":"d9c42ac9-4565-4ccb-99f3-8f35d5b9d135","html_url":"https://github.com/anthonycorletti/partykit-nextjs-monaco-editor-example","commit_stats":null,"previous_names":["anthonycorletti/partykit-nextjs-monaco-editor-example"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonycorletti%2Fpartykit-nextjs-monaco-editor-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonycorletti%2Fpartykit-nextjs-monaco-editor-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonycorletti%2Fpartykit-nextjs-monaco-editor-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonycorletti%2Fpartykit-nextjs-monaco-editor-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anthonycorletti","download_url":"https://codeload.github.com/anthonycorletti/partykit-nextjs-monaco-editor-example/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248799670,"owners_count":21163398,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bun","monaco-editor-react","nextjs","partykit","typescript"],"created_at":"2024-11-20T23:23:26.942Z","updated_at":"2025-04-13T23:44:48.856Z","avatar_url":"https://github.com/anthonycorletti.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# partykit-nextjs-monaco-editor-example\n\nThis is an example of how to leverage [partykit](http://github.com/partykit/partykit) and [monaco-react](https://github.com/suren-atoyan/monaco-react) to create a collaborative editor experience when you're building with [NextJS](http://github.com/vercel/next.js) and [`bun`](http://github.com/oven-sh/bun).\n\n## Getting Started\n\nIf you'd like to hit the ground running,\n\n1. Make sure you've installed `bun`. I'm using `1.0.30` for this example.\n1. Run the following:\n\n    ```bash\n    git clone https://github.com/anthonycorletti/partykit-nextjs-monaco-editor-example.git \u0026\u0026 cd partykit-nextjs-monaco-editor-example\n    bun install\n    bun dev # in one shell session \n    bun run pk-dev # in another shell session\n    ```\n\n## Starting from scratch\n\nIf you'd like to dig in a bit deeper, here's how I made this step by step if you'd like to follow along and extend or implement this for your own purposes:\n\n1. Create your nextjs app with bun\n\n    ```sh\n    \u003e bun create next-app\n    ✔ What is your project named? … partykit-nextjs-monaco-editor-example\n    ✔ Would you like to use TypeScript? … No / Yes\n    ✔ Would you like to use ESLint? … No / Yes\n    ✔ Would you like to use Tailwind CSS? … No / Yes\n    ✔ Would you like to use `src/` directory? … No / Yes\n    ✔ Would you like to use App Router? (recommended) … No / Yes\n    ✔ Would you like to customize the default import alias (@/*)? … No / Yes\n    ```\n\n1. Make sure that worked by running `bun dev` and viewing the new app in your browser.\n\n1. Now let's install our dependencies\n\n    ```sh\n    bun add @monaco-editor/react y-monaco y-partykit yjs partysocket\n    ```\n\n1. Let's make a new directory and file for our new code editor page.\n\n    ```sh\n    mkdir -p app/code\n    touch app/code/page.tsx\n    ```\n\n1. First, let's put some scaffolding in place. Drop this into `app/code/page.tsx`\n\n    ```tsx\n    \"use client\"\n\n    export default function EditorPage() {\n        return (\n            \u003cdiv className=\"flex flex-col min-h-screen\"\u003e\n                \u003ch1\u003eEditor\u003c/h1\u003e\n                \u003cp\u003eSome content\u003c/p\u003e\n            \u003c/div\u003e\n        )\n    }\n    ```\n\n    This tells next to consider this page a part of the client bundle, and exports our default function `EditorPage` so that it can return the HTML content to our browser. \n\n    If you go to http://localhost:3000/code, you should see this page.\n\n1. Now, let's fill out some more details. First, we'll import everything we need.\n\n    ```tsx\n    \"use client\"\n\n    import { useEffect } from 'react';\n    import Editor, { useMonaco } from '@monaco-editor/react';\n\n    import BetterWebSocket from \"partysocket/ws\";\n    import YPartyKitProvider from \"y-partykit/provider\";\n    import { MonacoBinding } from \"y-monaco\";\n    import * as Y from \"yjs\";\n\n    //...\n    ```\n\n1. And next we'll scaffold out more of our `EditorPage`.  First, let's set what we want to return.\n\n    ```tsx\n    //...\n\n    export default function EditorPage() {\n\n        //...\n\n        return (\n            \u003csection className=\"min-h-screen items-center justify-center mx-auto max-w-2xl space-y-2 m-5\"\u003e\n                \u003cEditor\n                    theme=\"vs-dark\"\n                    defaultLanguage=\"javascript\"\n                    defaultValue=\"// what good shall we do this day?\"\n                    className=\"bg-background h-[720px] shadow-lg\"\n                /\u003e\n                \u003cbutton id=\"y-connect-button\" className=\"px-4 py-3 bg-neutral-200 rounded font-medium hover:bg-neutral-300 transition duration-300 dark:bg-neutral-500 dark:hover:bg-neutral-600\"\u003e👋 Disconnect\u003c/button\u003e\n            \u003c/section\u003e\n        )\n    }\n    ```\n\n1. And now, we'll fill in some details to configure `monaco` in this function\n\n    ```tsx\n    // ...\n    export default function EditorPage() {\n        const monaco = useMonaco();\n\n        useEffect(() =\u003e {\n            if (typeof window !== \"undefined\") {\n                if (monaco) {\n                    console.log(\"hello monaco!\")\n                }\n            }\n        }, [monaco]);\n\n        //...\n    }\n    ```\n\n    We're using `useEffect` here because we have to fetch client side information in monaco that we want to feed to yjs and partykit.\n\n1. Solid! Now we should be able to see the editor appear when we navigate to http://localhost:3000/code. Open the browser's console and your should see `hello monaco!` too.\n\n1. Now, our final step, we're going to configure `yjs`, `y-monaco`,  and partykit.\n\n    ```tsx\n    export default function EditorPage() {\n        const monaco = useMonaco();\n\n        useEffect(() =\u003e {\n            if (typeof window !== \"undefined\") {\n                if (monaco) {\n                    // create a yew yjs doc\n                    const ydoc = new Y.Doc();\n                    // establish partykit as your websocket provider\n                    const provider = new YPartyKitProvider(\"http://localhost:1999\", \"nextjs-monaco-demo\", ydoc, {\n                        // @ts-expect-error TODO: fix this\n                        WebSocketPolyfill: BetterWebSocket\n                    });\n                    // send a readiness check to partykit\n                    provider.ws?.send(\"it's happening!\");\n                    // get the text from the monaco editor\n                    const yDocTextMonaco = ydoc.getText(\"monaco\");\n                    // get the monaco editor\n                    const editor = monaco.editor.getEditors()[0];\n                    // create the monaco binding to the yjs doc\n                    new MonacoBinding(\n                        yDocTextMonaco,\n                        editor.getModel()!,\n                        // @ts-expect-error TODO: fix this\n                        new Set([editor]),\n                        provider.awareness\n                    );\n                    // enable a button to connect and disconnect from partykit\n                    const connectButton = document.getElementById(\"y-connect-button\")!;\n                    connectButton.addEventListener(\"click\", () =\u003e {\n                        if (provider.shouldConnect) {\n                            provider.disconnect();\n                            connectButton.textContent = \"🎈 Connect\";\n                        } else {\n                            provider.connect();\n                            connectButton.textContent = \"👋 Disconnect\";\n                        }\n                    });\n                }\n            }\n        }, [monaco]);\n\n        //...\n    }\n    ```\n\n    Checkout the inline comments for more details on what's going on there!\n\n1. Almost there! Let's add some styling to the indicator so we can notice different people typing when we go to http://localhost:3000/code in another browser window or tab. Add the following css to `app/globals.css`\n\n    ```css\n    .yRemoteSelection {\n        background-color: rgb(250, 129, 0, 0.5);\n    }\n\n    .yRemoteSelectionHead {\n        position: absolute;\n        border-left: orange solid 2px;\n        border-top: orange solid 2px;\n        border-bottom: orange solid 2px;\n        height: 100%;\n        box-sizing: border-box;\n    }\n\n    .yRemoteSelectionHead::after {\n        position: absolute;\n        content: \" \";\n        border: 3px solid orange;\n        border-radius: 4px;\n        left: -4px;\n        top: -5px;\n    }\n    ```\n\n    Note that these won't change per user. I'm leaving that one for another time, but you should be able to do it by adding some user metadata to the monaco editor.\n\n\n1. Now let's integrate partykit!\n\n    ```\n    bunx partykit init\n    ```\n\n    Make sure to \"add to an existing project\"\n\n1. I like to keep my `ts` files in `lib/` so I moved the `party/main.ts` to `lib/pk.ts`, and replaced the content of that `lib/pk.ts` with the following.\n\n    ```ts\n    import type * as Party from \"partykit/server\";\n    import { onConnect } from \"y-partykit\";\n    import * as Y from \"yjs\";\n\n    export default class Server implements Party.Server {\n        constructor(public room: Party.Room) { }\n        async onConnect(connection: Party.Connection) {\n            const room = this.room;\n\n            await onConnect(connection, this.room, {\n                async load() {\n                    const doc = new Y.Doc();\n                    // NOTE: You could load data from a database into the doc here.\n                    return doc;\n                },\n                callback: {\n                    handler: async (doc) =\u003e {\n                        // This is called every few seconds if the document has changed\n                        // convert the Yjs document to a Uint8Array\n                        const content = Y.encodeStateAsUpdate(doc);\n                        // NOTE: You could save the content to a database here.\n                    },\n                },\n            });\n        }\n    }\n    ```\n\n1. Make sure that your `partykit.json` looks something like the following:\n\n    ```json\n    {\n        \"$schema\": \"https://www.partykit.io/schema.json\",\n        \"name\": \"partykit-nextjs-monaco-editor-example-party\",\n        \"main\": \"lib/pk.ts\",\n        \"compatibilityDate\": \"2024-03-14\"\n    }\n    ```\n\n1. As a convenience, I added `partykit dev --live` as a script in `package.json` under `pk-dev`\n\n1. Finally! In one shell session, run `bun dev`, and in another, run `bun run pk-dev`.\n\n1. Open http://localhost:3000/code in two tabs and/or windows in your browser, and notice that there are two different cursors! You should see each client update as you type in one. How awesome!\n\n1. Ah one last (optional) thing. If you noticed nextjs's server console, it's complaining about nested css in our code page. We can fix this by enabling nesting.\n\n    First, update our `postcss.confg.js` to look like the following\n\n    ```js\n    module.exports = {\n        plugins: {\n            'tailwindcss/nesting': 'postcss-nesting',\n            tailwindcss: {},\n            autoprefixer: {},\n        },\n    };\n    ```\n\n    And install `postcss-nesting`\n\n    ```sh\n    bun add postcss-nesting -D\n    ```\n\n    Voila! No more gnarly logs.\n\n## Learn More\n\nTo learn more about Next.js, take a look at the following resources:\n\n- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.\n- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.\n\nYou can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!\n\n## Deploy on Vercel\n\nThe easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template\u0026filter=next.js\u0026utm_source=create-next-app\u0026utm_campaign=create-next-app-readme) from the creators of Next.js.\n\nCheck out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanthonycorletti%2Fpartykit-nextjs-monaco-editor-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanthonycorletti%2Fpartykit-nextjs-monaco-editor-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanthonycorletti%2Fpartykit-nextjs-monaco-editor-example/lists"}