Advanced Example
Advanced Next.js integration with React Video Editor
Advanced Next.js Example
A comprehensive Next.js integration with React Video Editor featuring advanced features.
Overview
This example demonstrates advanced features including:
- Custom video adapters
- File upload handling
- Video processing
- Custom UI components
- State management
- API integration
Project Setup
1. Create Next.js Project
npx create-next-app@latest advanced-video-editor --typescript --tailwind --eslint
cd advanced-video-editor
2. Install Dependencies
npm install react-video-editor @types/node
npm install -D @types/react @types/react-dom
3. Create Advanced Video Editor Component
Create components/AdvancedVideoEditor.tsx
:
'use client';
import { useState, useCallback } from 'react';
import { ReactVideoEditor, VideoAdapter, ImageAdapter } from 'react-video-editor';
interface VideoProject {
id: string;
name: string;
videoUrl: string;
thumbnailUrl?: string;
}
export default function AdvancedVideoEditor() {
const [projects, setProjects] = useState<VideoProject[]>([]);
const [currentProject, setCurrentProject] = useState<VideoProject | null>(null);
const [isProcessing, setIsProcessing] = useState(false);
const handleFileUpload = useCallback(async (file: File) => {
setIsProcessing(true);
try {
// Handle file upload logic here
const videoUrl = await uploadVideo(file);
const newProject: VideoProject = {
id: Date.now().toString(),
name: file.name,
videoUrl,
};
setProjects(prev => [...prev, newProject]);
setCurrentProject(newProject);
} catch (error) {
console.error('Upload failed:', error);
} finally {
setIsProcessing(false);
}
}, []);
const handleExport = useCallback(async () => {
if (!currentProject) return;
setIsProcessing(true);
try {
// Handle export logic here
await exportVideo(currentProject);
} catch (error) {
console.error('Export failed:', error);
} finally {
setIsProcessing(false);
}
}, [currentProject]);
return (
<div className="flex h-screen">
{/* Sidebar */}
<div className="w-64 bg-gray-100 p-4">
<h2 className="text-lg font-semibold mb-4">Projects</h2>
<div className="space-y-2">
{projects.map(project => (
<div
key={project.id}
className={`p-2 rounded cursor-pointer ${
currentProject?.id === project.id ? 'bg-blue-200' : 'bg-white'
}`}
onClick={() => setCurrentProject(project)}
>
{project.name}
</div>
))}
</div>
<button
className="mt-4 w-full bg-blue-500 text-white p-2 rounded"
onClick={() => document.getElementById('file-input')?.click()}
disabled={isProcessing}
>
{isProcessing ? 'Processing...' : 'Upload Video'}
</button>
<input
id="file-input"
type="file"
accept="video/*"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) handleFileUpload(file);
}}
className="hidden"
/>
</div>
{/* Main Editor */}
<div className="flex-1">
{currentProject ? (
<div className="h-full">
<div className="h-12 bg-gray-200 flex items-center justify-between px-4">
<h1 className="text-lg font-semibold">{currentProject.name}</h1>
<button
className="bg-green-500 text-white px-4 py-2 rounded"
onClick={handleExport}
disabled={isProcessing}
>
{isProcessing ? 'Exporting...' : 'Export'}
</button>
</div>
<ReactVideoEditor
width="100%"
height="calc(100% - 48px)"
videoUrl={currentProject.videoUrl}
theme="dark"
language="en"
// Advanced configuration
adapters={{
video: new VideoAdapter(),
image: new ImageAdapter(),
}}
/>
</div>
) : (
<div className="h-full flex items-center justify-center">
<p className="text-gray-500">Select a project or upload a video to get started</p>
</div>
)}
</div>
</div>
);
}
// Mock functions - replace with actual implementations
async function uploadVideo(file: File): Promise<string> {
// Implement file upload logic
return URL.createObjectURL(file);
}
async function exportVideo(project: VideoProject): Promise<void> {
// Implement video export logic
console.log('Exporting video:', project.name);
}
4. Create API Routes
Create app/api/upload/route.ts
:
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
try {
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json({ error: 'No file provided' }, { status: 400 });
}
// Implement file upload logic here
// This could involve uploading to a cloud storage service
return NextResponse.json({
success: true,
url: `/uploads/${file.name}`
});
} catch (error) {
return NextResponse.json({ error: 'Upload failed' }, { status: 500 });
}
}
5. Update the Main Page
Update app/page.tsx
:
import AdvancedVideoEditor from '@/components/AdvancedVideoEditor';
export default function Home() {
return (
<main className="min-h-screen">
<AdvancedVideoEditor />
</main>
);
}
Advanced Features
Custom Adapters
import { CustomVideoAdapter } from './adapters/CustomVideoAdapter';
const customAdapter = new CustomVideoAdapter({
apiKey: process.env.CUSTOM_API_KEY,
baseUrl: 'https://api.example.com',
});
State Management
import { useReducer } from 'react';
interface EditorState {
currentTime: number;
duration: number;
isPlaying: boolean;
volume: number;
}
function editorReducer(state: EditorState, action: any): EditorState {
switch (action.type) {
case 'SET_CURRENT_TIME':
return { ...state, currentTime: action.payload };
case 'SET_DURATION':
return { ...state, duration: action.payload };
case 'SET_PLAYING':
return { ...state, isPlaying: action.payload };
case 'SET_VOLUME':
return { ...state, volume: action.payload };
default:
return state;
}
}
Error Boundaries
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error }: { error: Error }) {
return (
<div className="p-4 bg-red-100 border border-red-400 text-red-700 rounded">
<h2>Something went wrong:</h2>
<pre>{error.message}</pre>
</div>
);
}
// Wrap your component
<ErrorBoundary FallbackComponent={ErrorFallback}>
<AdvancedVideoEditor />
</ErrorBoundary>
Performance Optimizations
Dynamic Imports
import dynamic from 'next/dynamic';
const ReactVideoEditor = dynamic(
() => import('react-video-editor').then(mod => ({ default: mod.ReactVideoEditor })),
{ ssr: false }
);
Memoization
import { memo, useMemo } from 'react';
const VideoThumbnail = memo(({ videoUrl }: { videoUrl: string }) => {
const thumbnailUrl = useMemo(() => {
// Generate thumbnail logic
return `${videoUrl}?thumb=true`;
}, [videoUrl]);
return <img src={thumbnailUrl} alt="Video thumbnail" />;
});
File Structure
advanced-video-editor/
├── app/
│ ├── api/
│ │ └── upload/
│ │ └── route.ts
│ └── page.tsx
├── components/
│ ├── AdvancedVideoEditor.tsx
│ └── VideoThumbnail.tsx
├── adapters/
│ └── CustomVideoAdapter.ts
├── hooks/
│ └── useVideoEditor.ts
├── types/
│ └── video.ts
└── utils/
└── videoProcessing.ts
Next Steps
- Implement real file upload to cloud storage
- Add video processing with Web Workers
- Implement user authentication
- Add collaborative editing features
- Check out the Components for more customization options
Troubleshooting
Common Issues
- Memory Leaks: Ensure proper cleanup in useEffect
- Performance: Use dynamic imports and memoization
- File Upload: Handle large files with proper chunking
- State Management: Use appropriate state management patterns
Getting Help
- Check the Troubleshooting guide
- Review the Adapters documentation
- Open an issue on our GitHub repository