diff --git a/examples/tiptap-collab-editing/.eslintrc.js b/examples/tiptap-collab-editing/.eslintrc.js
new file mode 100644
index 00000000000..ced78085f86
--- /dev/null
+++ b/examples/tiptap-collab-editing/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
+};
diff --git a/examples/tiptap-collab-editing/.gitignore b/examples/tiptap-collab-editing/.gitignore
new file mode 100644
index 00000000000..3f7bf98da3e
--- /dev/null
+++ b/examples/tiptap-collab-editing/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+
+/.cache
+/build
+/public/build
+.env
diff --git a/examples/tiptap-collab-editing/README.md b/examples/tiptap-collab-editing/README.md
new file mode 100644
index 00000000000..52e1a4f9970
--- /dev/null
+++ b/examples/tiptap-collab-editing/README.md
@@ -0,0 +1,24 @@
+# Real time collaborative editing using tiptap and webrtc
+
+this is an example of using tiptap,yjs and webrtc to do real time collaborative editing across device and browsers
+
+## Relevent files
+
+```
+app
+├── routes
+│ └── editor.tsx // tiptab editor route, open it in different browser to see the changes get sync
+└── utils
+ └── webrtc.client.tsx //init the webrtc, ydoc and collaboration plugin on client
+```
+
+## Preview
+
+Open this example on [CodeSandbox](https://codesandbox.com):
+
+[](https://codesandbox.io/s/github/remix-run/remix/tree/main/examples/tiptap-collab-editing)
+
+## Related Links
+
+- [Collaborative editing – Tiptap Editor](https://tiptap.dev/guide/collaborative-editing#show-other-cursors)
+- [Remix | Module Constraints](https://remix.run/docs/en/v1/guides/constraints#document-guard)
diff --git a/examples/tiptap-collab-editing/app/entry.client.tsx b/examples/tiptap-collab-editing/app/entry.client.tsx
new file mode 100644
index 00000000000..3eec1fd0a02
--- /dev/null
+++ b/examples/tiptap-collab-editing/app/entry.client.tsx
@@ -0,0 +1,4 @@
+import { RemixBrowser } from "@remix-run/react";
+import { hydrate } from "react-dom";
+
+hydrate(, document);
diff --git a/examples/tiptap-collab-editing/app/entry.server.tsx b/examples/tiptap-collab-editing/app/entry.server.tsx
new file mode 100644
index 00000000000..5afa18235cc
--- /dev/null
+++ b/examples/tiptap-collab-editing/app/entry.server.tsx
@@ -0,0 +1,21 @@
+import type { EntryContext } from "@remix-run/node";
+import { RemixServer } from "@remix-run/react";
+import { renderToString } from "react-dom/server";
+
+export default function handleRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ remixContext: EntryContext
+) {
+ const markup = renderToString(
+
+ );
+
+ responseHeaders.set("Content-Type", "text/html");
+
+ return new Response("" + markup, {
+ status: responseStatusCode,
+ headers: responseHeaders,
+ });
+}
diff --git a/examples/tiptap-collab-editing/app/root.tsx b/examples/tiptap-collab-editing/app/root.tsx
new file mode 100644
index 00000000000..927a0f745df
--- /dev/null
+++ b/examples/tiptap-collab-editing/app/root.tsx
@@ -0,0 +1,32 @@
+import type { MetaFunction } from "@remix-run/node";
+import {
+ Links,
+ LiveReload,
+ Meta,
+ Outlet,
+ Scripts,
+ ScrollRestoration,
+} from "@remix-run/react";
+
+export const meta: MetaFunction = () => ({
+ charset: "utf-8",
+ title: "New Remix App",
+ viewport: "width=device-width,initial-scale=1",
+});
+
+export default function App() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/tiptap-collab-editing/app/routes/editor.tsx b/examples/tiptap-collab-editing/app/routes/editor.tsx
new file mode 100644
index 00000000000..4a7ec8d0880
--- /dev/null
+++ b/examples/tiptap-collab-editing/app/routes/editor.tsx
@@ -0,0 +1,32 @@
+import { useEditor, EditorContent } from "@tiptap/react";
+import StarterKit from "@tiptap/starter-kit";
+import { Collaboration, ydoc, WebrtcProvider } from "~/utils/webrtc.client";
+
+if (typeof document !== "undefined") {
+ //join the room remix example
+ new WebrtcProvider("room-remix-example", ydoc);
+}
+export default () => {
+ let editor;
+ if (typeof document !== "undefined") {
+ editor = useEditor({
+ extensions: [
+ StarterKit.configure({
+ history: false,
+ }),
+ Collaboration.configure({
+ document: ydoc,
+ }),
+ ],
+ });
+ }
+
+ return (
+
+
Open in two browsers and see the editing in sync
+
+ {editor && }
+
+
+ );
+};
diff --git a/examples/tiptap-collab-editing/app/utils/webrtc.client.tsx b/examples/tiptap-collab-editing/app/utils/webrtc.client.tsx
new file mode 100644
index 00000000000..bad914b518b
--- /dev/null
+++ b/examples/tiptap-collab-editing/app/utils/webrtc.client.tsx
@@ -0,0 +1,6 @@
+import Collaboration from "@tiptap/extension-collaboration";
+import * as Y from "yjs";
+import { WebrtcProvider } from "y-webrtc";
+const ydoc = new Y.Doc();
+
+export { Collaboration, WebrtcProvider, ydoc };
diff --git a/examples/tiptap-collab-editing/package.json b/examples/tiptap-collab-editing/package.json
new file mode 100644
index 00000000000..6e2a4d80bdc
--- /dev/null
+++ b/examples/tiptap-collab-editing/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "remix-example-template",
+ "private": true,
+ "description": "",
+ "license": "",
+ "sideEffects": false,
+ "scripts": {
+ "build": "remix build",
+ "dev": "remix dev",
+ "start": "remix-serve build"
+ },
+ "dependencies": {
+ "@remix-run/node": "1.3.5",
+ "@remix-run/react": "1.3.5",
+ "@remix-run/serve": "1.3.5",
+ "@tiptap/extension-collaboration": "^2.0.0-beta.33",
+ "@tiptap/react": "^2.0.0-beta.108",
+ "@tiptap/starter-kit": "^2.0.0-beta.183",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2",
+ "y-webrtc": "^10.2.2",
+ "yjs": "^13.5.34"
+ },
+ "devDependencies": {
+ "@remix-run/dev": "1.3.5",
+ "@remix-run/eslint-config": "1.3.5",
+ "@types/react": "^17.0.39",
+ "@types/react-dom": "^17.0.13",
+ "eslint": "^8.10.0",
+ "typescript": "^4.6.2"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+}
diff --git a/examples/tiptap-collab-editing/public/favicon.ico b/examples/tiptap-collab-editing/public/favicon.ico
new file mode 100644
index 00000000000..8830cf6821b
Binary files /dev/null and b/examples/tiptap-collab-editing/public/favicon.ico differ
diff --git a/examples/tiptap-collab-editing/remix.config.js b/examples/tiptap-collab-editing/remix.config.js
new file mode 100644
index 00000000000..1755894599a
--- /dev/null
+++ b/examples/tiptap-collab-editing/remix.config.js
@@ -0,0 +1,10 @@
+/**
+ * @type {import('@remix-run/dev').AppConfig}
+ */
+module.exports = {
+ ignoredRouteFiles: [".*"],
+ // appDirectory: "app",
+ // assetsBuildDirectory: "public/build",
+ // serverBuildPath: "build/index.js",
+ // publicPath: "/build/",
+};
diff --git a/examples/tiptap-collab-editing/remix.env.d.ts b/examples/tiptap-collab-editing/remix.env.d.ts
new file mode 100644
index 00000000000..72e2affe311
--- /dev/null
+++ b/examples/tiptap-collab-editing/remix.env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/examples/tiptap-collab-editing/sandbox.config.json b/examples/tiptap-collab-editing/sandbox.config.json
new file mode 100644
index 00000000000..4363d87a30d
--- /dev/null
+++ b/examples/tiptap-collab-editing/sandbox.config.json
@@ -0,0 +1,6 @@
+{
+ "hardReloadOnChange": true,
+ "container": {
+ "port": 3000
+ }
+}
diff --git a/examples/tiptap-collab-editing/tsconfig.json b/examples/tiptap-collab-editing/tsconfig.json
new file mode 100644
index 00000000000..749e4b959e8
--- /dev/null
+++ b/examples/tiptap-collab-editing/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
+ "compilerOptions": {
+ "lib": ["DOM", "DOM.Iterable", "ES2019"],
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "target": "ES2019",
+ "strict": true,
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./app/*"]
+ },
+
+ // Remix takes care of building everything in `remix build`.
+ "noEmit": true
+ }
+}