Menu
Products
Products
Video Hosting
Upload and manage your videos in a centralized video library.
Image Hosting
Upload and manage all your images in a centralized library.
Galleries
Choose from 100+templates to showcase your media in style.
Video Messaging
Record, and send personalized video messages.
CincoTube
Create your own community video hub your team, students or fans.
Pages
Create dedicated webpages to share your videos and images.
Live
Create dedicated webpages to share your videos and images.
For Developers
Video API
Build a unique video experience.
DeepUploader
Collect and store user content from anywhere with our file uploader.
Solutions
Solutions
Enterprise
Supercharge your business with secure, internal communication.
Townhall
Webinars
Team Collaboration
Learning & Development
Creative Professionals
Get creative with a built in-suite of editing and marketing tools.
eCommerce
Boost sales with interactive video and easy-embedding.
Townhall
Webinars
Team Collaboration
Learning & Development
eLearning & Training
Host and share course materials in a centralized portal.
Sales & Marketing
Attract, engage and convert with interactive tools and analytics.
"Cincopa helped my Enterprise organization collaborate better through video."
Book a Demo
Resources
Resources
Blog
Learn about the latest industry trends, tips & tricks.
Help Centre
Get access to help articles FAQs, and all things Cincopa.
Partners
Check out our valued list of partners.
Product Updates
Stay up-to-date with our latest greatest features.
Ebooks, Guides & More
Customer Stories
Hear how we've helped businesses succeed.
Boost Campaign Performance Through Video
Discover how to boost your next campaign by using video.
Download Now
Pricing
Log in
Get a demo
Get Started
Building a Loom-style screen recorder with Next.js, Stream, and Firebase brings together tools that make real-time video capture and sharing possible in a modern web app. It allows creators, teams, and users to record their screen, upload the video instantly, and access it from anywhere without any heavy setup. This kind of system matters because people rely on fast visual communication, and a smooth recording flow removes friction from everyday work. By combining these technologies, we can shape a lightweight video workflow that feels quick, reliable, and easy to maintain. Prerequisites 1. Create Next.js App : npx create-next-app@latest loom-clone --typescript --tailwind --eslint cd loom-clone 2. Install Dependencies : npm install firebase npm install -D @types/node 3. Firebase Setup : 1. Firebase Console → New Project 2. Enable Auth (Email/Password) + Storage + Firestore 3. Storage Rules: allow read, write: if request.auth != null; 4. Download Firebase config → src/lib/firebase.ts 4. pubspec.yaml → firebase.ts (complete config) : // src/lib/firebase.ts import { initializeApp } from 'firebase/app'; import { getAuth } from 'firebase/auth'; import { getStorage } from 'firebase/storage'; import { getFirestore } from 'firebase/firestore'; const firebaseConfig = { apiKey: 'YOUR_API_KEY', authDomain: 'YOUR_PROJECT.firebaseapp.com', projectId: 'YOUR_PROJECT_ID', // ... full config from Firebase Console }; const app = initializeApp(firebaseConfig); export const auth = getAuth(app); export const storage = getStorage(app); export const db = getFirestore(app); Building a Loom Clone Using Next.js, Stream, and Firebase Building a Loom clone with Next.js, Stream, and Firebase brings together the tools needed for smooth screen recording, instant uploads, and simple playback. It creates a workflow where video capture and access feel direct and dependable, helping users share clear visual messages without extra effort. Authentication (app/login/page.tsx) 'use client'; import { useState } from 'react'; import { signInWithEmailAndPassword, createUserWithEmailAndPassword } from 'firebase/auth'; import { auth } from '@/lib/firebase'; import { useRouter } from 'next/navigation'; export default function Login() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [isLogin, setIsLogin] = useState(true); const [loading, setLoading] = useState(false); const router = useRouter(); const handleAuth = async () => { setLoading(true); try { if (isLogin) { await signInWithEmailAndPassword(auth, email, password); } else { await createUserWithEmailAndPassword(auth, email, password); } router.push('/dashboard'); } catch (error) { console.error('Auth failed:', error); } finally { setLoading(false); } }; return (
{isLogin ? 'Login' : 'Sign Up'}
setEmail(e.target.value)} className='w-full p-3 mb-4 border rounded-lg' />
setPassword(e.target.value)} className='w-full p-3 mb-4 border rounded-lg' />
{loading ? 'Loading...' : (isLogin ? 'Login' : 'Sign Up')}
setIsLogin(!isLogin)} className='w-full mt-2 text-blue-500 hover:underline' > {isLogin ? 'Need an account? Sign Up' : 'Have an account? Login'}
); } Dashboard (app/dashboard/page.tsx) 'use client'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { onAuthStateChanged } from 'firebase/auth'; import { collection, query, where, onSnapshot, deleteDoc, doc } from 'firebase/firestore'; import { ref, deleteObject } from 'firebase/storage'; import { auth, db, storage } from '@/lib/firebase'; interface Video { id: string; title: string; url: string; thumbnail: string; duration: number; createdAt: Date; } export default function Dashboard() { const [videos, setVideos] = useState
([]); const [loading, setLoading] = useState(true); const router = useRouter(); useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { if (!user) router.push('/login'); }); if (auth.currentUser) { const q = query( collection(db, 'videos'), where('userId', '==', auth.currentUser!.uid) ); const unsubscribeVideos = onSnapshot(q, (snapshot) => { const videoList: Video[] = []; snapshot.forEach((doc) => { videoList.push({ id: doc.id, ...doc.data() } as Video); }); setVideos(videoList); setLoading(false); }); return () => unsubscribeVideos(); } return unsubscribe; }, [router]); const deleteVideo = async (videoId: string, storagePath: string) => { try { await deleteDoc(doc(db, 'videos', videoId)); await deleteObject(ref(storage, storagePath)); } catch (error) { console.error('Delete failed:', error); } }; if (loading) return
Loading...
; return (
Your Videos
+ Record New
{videos.map((video) => (
{video.title}
{new Date(video.createdAt.toDate()).toLocaleDateString()}
Play
deleteVideo(video.id, `videos/${video.id}.webm`)} className='bg-gray-500 text-white py-2 px-4 rounded hover:bg-gray-600' > Delete
))}
); } Screen Recorder (app/record/page.tsx) 'use client'; import { useState, useRef, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { ref, uploadBytes, getDownloadURL } from 'firebase/storage'; import { collection, addDoc, serverTimestamp } from 'firebase/firestore'; import { useAuthState } from 'react-firebase-hooks/auth'; import { auth, storage, db } from '@/lib/firebase'; export default function Record() { const [user] = useAuthState(auth); const router = useRouter(); const [isRecording, setIsRecording] = useState(false); const [isPreviewing, setIsPreviewing] = useState(false); const [recordedChunks, setRecordedChunks] = useState
([]); const videoRef = useRef
(null); const mediaRecorderRef = useRef
(null); const streamRef = useRef
(null); const startRecording = useCallback(async () => { try { const stream = await navigator.mediaDevices.getDisplayMedia({ video: { cursor: 'always', resizeMode: 'crop-and-scale', }, audio: { echoCancellation: true, noiseSuppression: true, sampleRate: 44100, }, }); streamRef.current = stream; videoRef.current!.srcObject = stream; const recorder = new MediaRecorder(stream, { mimeType: 'video/webm;codecs=vp9', }); const chunks: BlobPart[] = []; recorder.ondataavailable = (e) => chunks.push(e.data); recorder.onstop = () => { setRecordedChunks(chunks); setIsPreviewing(true); stream.getTracks().forEach(track => track.stop()); }; mediaRecorderRef.current = recorder; recorder.start(250); // 250ms chunks setIsRecording(true); } catch (error) { console.error('Recording failed:', error); } }, []); const stopRecording = () => { if (mediaRecorderRef.current) { mediaRecorderRef.current.stop(); setIsRecording(false); } }; const uploadVideo = async () => { if (!recordedChunks.length || !user) return; const videoBlob = new Blob(recordedChunks, { type: 'video/webm' }); const videoRefPath = ref(storage, `videos/${user.uid}_${Date.now()}.webm`); try { // Upload video await uploadBytes(videoRefPath, videoBlob); const videoUrl = await getDownloadURL(videoRefPath); // Save metadata await addDoc(collection(db, 'videos'), { userId: user.uid, title: `Recording ${new Date().toLocaleString()}`, url: videoUrl, thumbnail: videoUrl, // Generate thumbnail server-side in production duration: videoBlob.size / 1000000, // MB createdAt: serverTimestamp(), }); router.push('/dashboard'); } catch (error) { console.error('Upload failed:', error); } }; if (!user) return
Please log in
; return (
Screen Recorder
{!isPreviewing ? (
{!isRecording ? (
Start Recording
) : ( <>
Stop Recording
● Recording...
> )}
) : (
Upload to Dashboard
)}
); } Production Testing Checklist Testing verifies that recording, uploading, and playback behave as expected in real use. Running the app end-to-end helps confirm that every part works together without errors. 1. npm run dev → localhost:3000/login 2. Sign up → /dashboard (empty) 3. /record → Share screen → Record 10s → Stop → Preview → Upload 4. Dashboard → See thumbnail → Play video 5. Share URL → Works in incognito 6. Delete Video → Gone from dashboard + Storage Production Deployment : 1. firebase deploy (Hosting + Functions) 2. Generate thumbnails (Cloud Function) 3. Add HLS transcoding (FFmpeg) 4. Custom domain + SSL 5. Scales to 10k+ users [web:151][web:152]