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.

WebRTC13 min readApril 14, 2026

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.

Next.jsWebRTCApp RouterSIPBrowser Calling

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.

Back to Blog

Need help implementing this for your project?

Talk to a VoIP Engineer