A real-time, peer-to-peer video calling application built with Next.js and WebRTC.
A real-time, peer-to-peer video calling application built with Next.js and WebRTC. Create or join rooms instantly โ no account required.
RTCDataChannel (no server relay)| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| UI | React 19, Tailwind CSS v4, Lucide icons |
| Real-time | WebRTC (RTCPeerConnection, RTCDataChannel) |
| Signaling | Socket.io v4 (custom Node.js server) |
| Language | TypeScript |
| Fonts | Geist Sans / Geist Mono |
Browser A Signaling Server (server.js) Browser B
| | |
|โโ join-room โโโโโโโโโโโโโโโโโโโโโ>| |
|<โ room-users (empty) โโโโโโโโโโโโโ| |
| |<โโโโโโโโโโโโ join-room โโโโโโโ|
|<โ user-joined โโโโโโโโโโโโโโโโโโโ |โโโ room-users ([A]) โโโโโโโโโ>|
| | |
|โโ offer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ >|
|<โ answer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ |
|<โโโโโโโโโโโโโโโโโโโโ ICE candidates (both ways) โโโโโโโโโโโโโโโโโ>|
| |
|<โโโโโโโโโโโโโโโโโโโ P2P media + data channel โโโโโโโโโโโโโโโโโโโ>|
(no server involved)
RTCDataChannel โ Chat messages travel peer-to-peer. The server never sees message content.replaceTrack โ Swaps the video track on all existing peer connections without renegotiation.server.js wraps Next.js with http.createServer, allowing Socket.io to share the same port.web-rtc/
โโโ server.js # Custom Node.js server (Next.js + Socket.io)
โโโ signaling-server/ # Standalone signaling server (for split deployments)
โ โโโ server.js
โ โโโ package.json
โโโ src/
โ โโโ app/
โ โ โโโ layout.tsx # Root layout (fonts, metadata)
โ โ โโโ page.tsx # Home page (Create Room / Join Room)
โ โ โโโ globals.css # Global styles + Tailwind v4
โ โ โโโ room/
โ โ โโโ [roomId]/
โ โ โโโ page.tsx # Room route (async params)
โ โ โโโ _components/
โ โ โโโ RoomClient.tsx # Main room orchestrator
โ โ โโโ PreJoin.tsx # Name entry screen before joining
โ โ โโโ VideoTile.tsx # Individual video/avatar tile
โ โ โโโ Controls.tsx # Bottom control bar
โ โ โโโ ChatPanel.tsx # Slide-in chat panel
โ โโโ hooks/
โ โ โโโ useWebRTC.ts # Core WebRTC hook (connections, signaling, media)
โ โโโ lib/
โ โ โโโ webrtc.ts # RTCConfiguration (STUN servers)
โ โโโ types/
โ โโโ index.ts # Shared TypeScript types
โโโ .env.local # Local environment variables (gitignored)
โโโ .env.example # Environment variable template
โโโ next.config.ts # Next.js config
npm install
cp .env.example .env.local
# .env.local defaults work for local development โ no changes needed
npm run dev
Open http://localhost:3000.
Note:
npm run devrunsnode server.js, notnext dev. This is required because Socket.io needs a persistent Node.js HTTP server.
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Port for the Node.js server |
NEXT_PUBLIC_SOCKET_URL |
(same origin) | URL of the signaling server. Leave empty in dev. Set to your deployed signaling server URL in production. |
FRONTEND_URL |
http://localhost:3000 |
(Signaling server only) Allowed CORS origin |
Railway supports persistent Node.js processes. No changes needed to the code.
NODE_ENV=productionnpm run build and npm run startVercel is serverless and cannot run Socket.io. Split the deployment:
signaling-server/ to Railway as a separate service
FRONTEND_URL=https://your-app.vercel.app in Railway env varsNEXT_PUBLIC_SOCKET_URL=https://your-signal.railway.app in Vercel env varsnpm run buildWhy not Vercel for everything? Vercelโs serverless functions are stateless and terminate between requests. Socket.io requires a persistent process to maintain WebSocket connections and room state.