# Framework Integrations Official quickstarts and patterns for popular frameworks. ## Next.js (App Router) Official repo: [zoom/videosdk-nextjs-quickstart](https://github.com/zoom/videosdk-nextjs-quickstart) ### Project Structure ``` app/ ├── api/ │ └── signature/ │ └── route.ts # Server-side JWT generation ├── video/ │ └── page.tsx # Video call component ├── layout.tsx └── page.tsx .env.local ├── ZOOM_SDK_KEY="your-key" └── ZOOM_SDK_SECRET="your-secret" ``` ### Server-Side JWT Generation (App Router) ```typescript // app/api/signature/route.ts import { NextRequest, NextResponse } from 'next/server'; import KJUR from 'jsrsasign'; export async function POST(request: NextRequest) { const { topic, role } = await request.json(); const iat = Math.floor(Date.now() / 1000) - 30; const exp = iat + 60 * 60 * 2; // 2 hours const header = { alg: 'HS256', typ: 'JWT' }; const payload = { app_key: process.env.ZOOM_SDK_KEY, tpc: topic, role_type: role || 1, version: 1, iat, exp, }; const signature = KJUR.jws.JWS.sign( 'HS256', JSON.stringify(header), JSON.stringify(payload), process.env.ZOOM_SDK_SECRET! ); return NextResponse.json({ signature }); } ``` ### Client Component (App Router) ```tsx // app/video/page.tsx 'use client'; import { useEffect, useState, useRef } from 'react'; import ZoomVideo, { VideoClient, Stream, VideoQuality } from '@zoom/videosdk'; export default function VideoPage() { const [client, setClient] = useState(null); const [stream, setStream] = useState(null); const [isJoined, setIsJoined] = useState(false); const containerRef = useRef(null); useEffect(() => { const init = async () => { const zmClient = ZoomVideo.createClient(); await zmClient.init('en-US', 'Global', { patchJsMedia: true }); setClient(zmClient); }; init(); return () => { ZoomVideo.destroyClient(); }; }, []); const joinSession = async (topic: string, userName: string) => { if (!client) return; // Get signature from API route const res = await fetch('/api/signature', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ topic, role: 1 }), }); const { signature } = await res.json(); // Join session await client.join(topic, signature, userName); const mediaStream = client.getMediaStream(); setStream(mediaStream); setIsJoined(true); }; const startVideo = async () => { if (!stream || !client) return; await stream.startVideo(); const currentUser = client.getCurrentUserInfo(); const element = await stream.attachVideo( currentUser.userId, VideoQuality.Video_360P ); containerRef.current?.appendChild(element); }; return (
{!isJoined ? ( ) : ( <>
)}
); } ``` ### SSR Considerations **CRITICAL**: The Video SDK uses WebAssembly and must run client-side only. ```tsx // ❌ WRONG: Importing at top level causes SSR issues import ZoomVideo from '@zoom/videosdk'; // ✅ CORRECT: Dynamic import or use 'use client' directive 'use client'; // Or use dynamic import const ZoomVideo = dynamic(() => import('@zoom/videosdk'), { ssr: false }); ``` --- ## Next.js (Pages Router) Branch: [pages-router](https://github.com/zoom/videosdk-nextjs-quickstart/tree/pages-router) ### Server-Side JWT (Pages Router) ```typescript // pages/api/signature.ts import type { NextApiRequest, NextApiResponse } from 'next'; import KJUR from 'jsrsasign'; export default function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } const { topic, role } = req.body; const iat = Math.floor(Date.now() / 1000) - 30; const exp = iat + 60 * 60 * 2; const header = { alg: 'HS256', typ: 'JWT' }; const payload = { app_key: process.env.ZOOM_SDK_KEY, tpc: topic, role_type: role || 1, version: 1, iat, exp, }; const signature = KJUR.jws.JWS.sign( 'HS256', JSON.stringify(header), JSON.stringify(payload), process.env.ZOOM_SDK_SECRET! ); res.status(200).json({ signature }); } ``` ### Client Component (Pages Router) ```tsx // pages/video.tsx import { useEffect, useState } from 'react'; import dynamic from 'next/dynamic'; // Dynamic import to prevent SSR issues const VideoComponent = dynamic(() => import('../components/Video'), { ssr: false, }); export default function VideoPage() { return ; } ``` --- ## Vue 3 / Nuxt 3 Official repo: [zoom/videosdk-vue-nuxt-quickstart](https://github.com/zoom/videosdk-vue-nuxt-quickstart) ### Composition API Pattern ```vue ``` ### Nuxt 3 Plugin (Client-Only) ```typescript // plugins/videosdk.client.ts import ZoomVideo from '@zoom/videosdk'; export default defineNuxtPlugin(() => { return { provide: { zoomVideo: ZoomVideo, }, }; }); ``` ```vue ``` --- ## Zoom For Government (ZFG) If using Zoom For Government, you need a separate SDK key from [marketplace.zoomgov.com](https://marketplace.zoomgov.com/). ### Option 1: ZFG-Specific Package Version ```json { "dependencies": { "@zoom/videosdk": "1.11.0-zfg" } } ``` ```javascript client.init('en-US', 'Global'); ``` ### Option 2: Custom WebEndpoint ```javascript client.init('en-US', 'https://source.zoomgov.com/videosdk/1.11.0/lib', { webEndpoint: 'www.zoomgov.com', }); ``` --- ## Official Sample Repositories | Framework | Repository | Branch | |-----------|------------|--------| | Vanilla JS/TS | [videosdk-web-sample](https://github.com/zoom/videosdk-web-sample) | master | | React | [videosdk-react](https://github.com/zoom/videosdk-react) | main | | Next.js (App) | [videosdk-nextjs-quickstart](https://github.com/zoom/videosdk-nextjs-quickstart) | app-router | | Next.js (Pages) | [videosdk-nextjs-quickstart](https://github.com/zoom/videosdk-nextjs-quickstart) | pages-router | | Vue/Nuxt | [videosdk-vue-nuxt-quickstart](https://github.com/zoom/videosdk-vue-nuxt-quickstart) | main | | UI Toolkit (React) | [videosdk-zoom-ui-toolkit-react-sample](https://github.com/zoom/videosdk-zoom-ui-toolkit-react-sample) | main | | Auth Endpoint | [videosdk-auth-endpoint-sample](https://github.com/zoom/videosdk-auth-endpoint-sample) | main | --- ## Common Patterns Across Frameworks ### 1. Client-Side Only The SDK must run client-side. All frameworks need to handle this: | Framework | Solution | |-----------|----------| | Next.js | `'use client'` or `dynamic(..., { ssr: false })` | | Nuxt 3 | `.client.ts` plugin or `` | | Vue SPA | No special handling needed | ### 2. Lifecycle Management ```typescript // Always follow this order: const client = ZoomVideo.createClient(); await client.init(...); await client.join(...); const stream = client.getMediaStream(); // ONLY after join() // Cleanup on unmount await client.leave(); ZoomVideo.destroyClient(); ``` ### 3. Event-Driven Video Rendering ```typescript // All frameworks should use this pattern client.on('peer-video-state-change', async ({ action, userId }) => { if (action === 'Start') { const el = await stream.attachVideo(userId, VideoQuality.Video_360P); container.appendChild(el); } else { await stream.detachVideo(userId); } }); ``` ## Related Documentation - [React Hooks](react-hooks.md) - @zoom/videosdk-react - [Session Join](session-join-pattern.md) - Core SDK patterns - [Video Rendering](video-rendering.md) - attachVideo() details