WebRTC in Next.js 15: Browser-to-Browser Calling with App Router
Build browser-based WebRTC calling in Next.js 15 App Router: RTCPeerConnection setup, signalling with Server-Sent Events, STUN/TURN configuration, and integrating with a SIP backend via a WebRTC-SIP gateway.
WebRTC in Next.js 15: Browser-to-Browser Calling with App Router
Build browser-based WebRTC calling in Next.js 15 App Router: RTCPeerConnection setup, signalling with Server-Sent Events, STUN/TURN configuration, and integrating with a SIP backend via a WebRTC-SIP gateway.
Kaushik Parmar
Founder & VoIP Architect, CelloIP Technologies
Why Next.js for WebRTC?
Next.js 15 with the App Router is an excellent choice for WebRTC-based communication platforms. App Router's React Server Components keep signalling logic server-side, while Client Components handle RTCPeerConnection and audio/video streams. Route Handlers replace the need for a separate Express signalling server. Key insight: WebRTC media flows peer-to-peer (or via TURN relay) — Next.js handles signalling only, never the audio/video data itself.
RTCPeerConnection in a Next.js Client Component
RTCPeerConnection must live in a Client Component ('use client') because it accesses browser APIs. The connection lifecycle is managed with useRef (persist across renders) and useEffect (setup/teardown). Next.js Route Handlers serve as SSE endpoints for signalling — clients subscribe to a call session and receive SDP offers, answers, and ICE candidates from the server.
app/call/[roomId]/page.tsx — WebRTC peer connection
'use client';
import { useEffect, useRef } from 'react';
export default function CallPage({ params }: { params: { roomId: string } }) {
const pcRef = useRef<RTCPeerConnection | null>(null);
useEffect(() => {
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:turn.example.com:3478',
username: 'user', credential: 'pass' },
],
});
pcRef.current = pc;
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => stream.getTracks()
.forEach(t => pc.addTrack(t, stream)));
pc.onicecandidate = ({ candidate }) => {
if (candidate) fetch(`/api/signal/${params.roomId}`, {
method: 'POST',
body: JSON.stringify({ type: 'ice-candidate', candidate }),
});
};
return () => pc.close();
}, [params.roomId]);
}Signalling with Route Handlers and Connecting to SIP
Next.js Route Handlers at app/api/signal/[roomId]/route.ts hold SSE subscribers per room. For production with multiple Next.js instances, use Redis pub/sub to share signals across servers. To connect WebRTC to existing SIP infrastructure (Asterisk, FreeSWITCH), use a WebRTC-SIP gateway — the browser uses WebRTC to the gateway, which translates to SIP for the PSTN leg.
Frequently Asked Questions
Can Next.js replace a dedicated WebRTC signalling server?
Yes for most use cases. Route Handlers with SSE or WebSocket handle signalling well. For very high scale (10,000+ concurrent rooms), a dedicated signalling service with Redis pub/sub is more appropriate.
What TURN server should I use?
Coturn is the standard self-hosted TURN server. Twilio Network Traversal Service and Metered.ca provide managed global TURN. Always use TURN — STUN alone fails in 15–20% of real-world networks.
Does Next.js work with SIP systems?
Next.js doesn't speak SIP natively, but integrates with a WebRTC-SIP gateway. Asterisk has built-in WebRTC support via chan_pjsip; FreeSWITCH via mod_verto or mod_sofia.
Need help implementing this for your project?
Talk to a VoIP Engineer