Realtime Monaco
Real-time Monaco editor for collaborative applications
Installation
Folder structure
1'use client'
2
3import { Editor } from '@monaco-editor/react'
4import { SupabasePersistenceOptions } from '@supabase-labs/y-supabase'
5import { Awareness } from 'y-protocols/awareness.js'
6
7import { useConnectOnMount } from '../hooks/use-connect-on-mount'
8
9type RealtimeMonacoProps = {
10 channel: string
11 language?: string
12 height?: string | number
13 className?: string
14 awareness?: boolean | Awareness
15 persistence?: boolean | SupabasePersistenceOptions
16 theme?: 'light' | 'dark'
17}
18
19const DEFAULT_HEIGHT = 550
20
21const RealtimeMonaco = ({
22 channel,
23 language = 'javascript',
24 height = DEFAULT_HEIGHT,
25 awareness = true,
26 persistence,
27 theme,
28 ...rest
29}: RealtimeMonacoProps) => {
30 const { connectOnMount } = useConnectOnMount({ channel, persistence, awareness })
31
32 return (
33 <Editor
34 height={height}
35 language={language}
36 theme={theme === 'dark' ? 'vs-dark' : 'light'}
37 onMount={connectOnMount}
38 {...rest}
39 />
40 )
41}
42
43export { RealtimeMonaco }Introduction
The Realtime Monaco component provides a collaborative code editor powered by Monaco and Yjs. It uses @supabase-labs/y-supabase under the hood to sync document state across clients through Supabase Realtime.
Features
- Real-time document synchronization via Supabase Realtime broadcast
- Cursor and selection sharing between collaborators (awareness)
- Optional persistence to Postgres so documents survive page reloads
- Supports all Monaco languages and themes
- Room-based isolation for scoped collaboration
How it works under the hood
The component creates a Yjs document and connects it to a Supabase Realtime channel using SupabaseProvider from @supabase-labs/y-supabase. A MonacoBinding from y-monaco bridges the Yjs document with the Monaco editor model, keeping them in sync.
When awareness is enabled, each user's cursor position and selection are broadcast to other clients in the same channel. Remote cursors are rendered with unique colors via dynamically injected CSS.
When persistence is enabled, the full Yjs document state is saved to a Postgres table so it can be restored when clients reconnect.
Usage
Basic usage
import { RealtimeMonaco } from '@/components/realtime-monaco'
export default function MonacoPage() {
return <RealtimeMonaco channel="realtime-monaco-demo" language="typescript" />
}With persistence
Enable persistence to save the editor content to your Supabase database. This requires a table to store the Yjs document state.
First, create the required table in your Supabase project:
create table yjs_documents (
room text primary key,
state text not null
);Then pass persistence to the component:
import { RealtimeMonaco } from '@/components/realtime-monaco'
export default function MonacoPage() {
return <RealtimeMonaco channel="realtime-monaco-demo" language="typescript" persistence />
}You can also pass a SupabasePersistenceOptions object:
<RealtimeMonaco
channel="realtime-monaco-demo"
language="typescript"
persistence={{
table: 'yjs_documents',
roomColumn: 'room',
stateColumn: 'state',
storeTimeout: 2000,
}}
/>Without awareness
By default, cursor and selection positions are shared between collaborators. To disable this:
<RealtimeMonaco channel="realtime-monaco-demo" awareness={false} />Props
| Prop | Type | Description |
|---|---|---|
channel | string | Unique channel name used to sync editor content between collaborators in the same session. |
language? | string | Monaco language identifier (e.g. typescript, python). Defaults to javascript. |
height? | string | number | Height of the editor container. Accepts a pixel number or CSS string (e.g. "100%"). Defaults to 550. |
className? | string | CSS class applied to the editor wrapper element. |
awareness? | boolean | Awareness | Enables cursor and selection sharing between users. Pass false to disable or a custom Awareness instance. Defaults to true. |
persistence? | boolean | SupabasePersistenceOptions | Persists editor content to Supabase so it survives page reloads. Pass true for defaults or an options object for fine-grained control. |
theme? | 'light' | 'dark' | Color theme for the editor. Maps to Monaco's light and vs-dark themes. |
SupabasePersistenceOptions
| Option | Type | Default | Description |
|---|---|---|---|
table | string | 'yjs_documents' | Name of the Postgres table used to store documents. |
schema | string | 'public' | Schema where the table is located. |
roomColumn | string | 'room' | Column used as the document identifier. |
stateColumn | string | 'state' | Column used to store the binary Yjs state. |
storeTimeout | number | 1000 | Debounce delay (ms) before persisting changes. |