// Modified by konlyzx (2026) - Updated project name from Stage to Better Flow; removed Prisma/PostgreSQL references; updated environment variables; added R2 storage and Chrome extension integration // Base project structure under Apache License 2.0 (Copyright 2025 Kartik Labhshetwar)
Better Flow is a modern web-based canvas editor built with Next.js 16 and React 19. It enables users to create stunning visual designs by uploading images, adding text overlays, customizing backgrounds, and exporting high-quality graphics—all in the browser.
- Next.js 16 - React framework with App Router
- React 19 - UI library with React Compiler enabled
- TypeScript - Type safety throughout the codebase
- Konva/React-Konva - 2D canvas rendering engine for user images and overlays
- html2canvas - DOM-to-canvas conversion for background rendering
- modern-screenshot - 3D transform capture for perspective effects
- Zustand - Lightweight state management with two main stores:
useImageStore- Main image and design state (with Zundo undo/redo)useEditorStore- Canvas rendering state (synced with image store)
- Custom Animation Engine - Keyframe interpolation with 8 easing functions
- FFmpeg WASM - Multi-threaded video encoding (H.264, WebM, GIF)
- WebCodecs API - Hardware-accelerated H.264 encoding with mp4-muxer
- MediaRecorder API - Native WebM recording fallback
- Tailwind CSS 4 - Utility-first CSS framework
- Radix UI - Accessible component primitives
- Hugeicons - Icon library
- Cloudflare R2 - Object storage for assets (backgrounds, videos)
- IndexedDB - Client-side storage for drafts and exports
- Sharp - Server-side image processing (dev dependency)
- Drizzle ORM - Database layer (D1/SQLite support, currently unused)
better-flow/
├── app/ # Next.js App Router pages
│ ├── api/ # API routes
│ │ ├── cleanup-cache/ # Cache cleanup endpoint
│ │ ├── export/ # Image export endpoint
│ │ ├── image-proxy/ # R2 image proxy
│ │ ├── upload-url/ # R2 presigned URL generation
│ │ └── upload-video/ # Chrome extension video upload
│ ├── editor/ # Editor page (root)
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Landing page
│ └── globals.css # Global styles
│
├── components/ # React components
│ ├── canvas/ # Canvas rendering components
│ │ ├── ClientCanvas.tsx # Main Konva canvas renderer
│ │ ├── CanvasContext.tsx # Canvas state management
│ │ └── EditorCanvas.tsx # Canvas wrapper component
│ ├── controls/ # Editor control panels
│ │ ├── BorderControls.tsx
│ │ ├── ShadowControls.tsx
│ │ ├── Perspective3DControls.tsx
│ │ └── UploadDropzone.tsx
│ ├── editor/ # Editor layout components
│ │ ├── EditorLayout.tsx
│ │ ├── editor-left-panel.tsx
│ │ └── unified-right-panel.tsx
│ ├── timeline/ # Animation timeline components
│ │ ├── TimelineEditor.tsx # Main timeline UI
│ │ ├── TimelineControls.tsx # Play/pause/loop controls
│ │ ├── TimelineRuler.tsx # Time ruler with ticks
│ │ ├── TimelineTrack.tsx # Animation track display
│ │ ├── TimelinePlayhead.tsx # Draggable playhead
│ │ ├── KeyframeMarker.tsx # Keyframe indicators
│ │ ├── AnimationPresetGallery.tsx # Preset browser
│ │ └── hooks/useTimelinePlayback.tsx # Playback engine
│ ├── export/ # Export UI components
│ ├── overlays/ # Overlay management
│ ├── presets/ # Preset selector
│ ├── text-overlay/ # Text overlay controls
│ ├── landing/ # Landing page components
│ └── ui/ # Reusable UI components (shadcn/ui)
│
├── lib/ # Core libraries and utilities
│ ├── store/ # Zustand stores
│ │ └── index.ts # useImageStore & useEditorStore
│ ├── animation/ # Animation engine
│ │ ├── interpolation.ts # Keyframe interpolation & easing
│ │ └── presets.ts # 20+ animation presets
│ ├── export/ # Export functionality
│ │ ├── export-service.ts # Image export logic
│ │ ├── export-slideshow-video.ts # Video export orchestrator
│ │ ├── ffmpeg-encoder.ts # FFmpeg WASM encoder
│ │ ├── webcodecs-encoder.ts # WebCodecs H.264 encoder
│ │ ├── video-encoder.ts # MediaRecorder wrapper
│ │ └── export-utils.ts # Export utilities
│ ├── constants/ # Configuration constants
│ │ ├── aspect-ratios.ts
│ │ ├── backgrounds.ts
│ │ ├── fonts.ts
│ │ ├── gradient-colors.ts
│ │ ├── presets.ts
│ │ └── solid-colors.ts
│ ├── canvas/ # Canvas utilities
│ ├── image-storage.ts # IndexedDB image storage
│ └── export-storage.ts # IndexedDB export storage
│
├── hooks/ # Custom React hooks
│ ├── useExport.ts # Export hook
│ ├── useCanvas.ts # Canvas operations hook
│ └── useAspectRatioDimensions.ts
│
├── types/ # TypeScript type definitions
│ ├── canvas.ts
│ ├── editor.ts
│ └── animation.ts # Animation, timeline, keyframe types
│
└── public/ # Static assets
├── assets/ # Demo images
├── overlays/ # Overlay images
└── backgrounds/ # Background images
The application uses a dual-store pattern with Zustand:
Manages the main design state:
- Uploaded image URL and metadata
- Background configuration (gradient, solid, image)
- Text overlays array
- Image overlays array
- Image transformations (scale, opacity, border radius)
- Border and shadow configurations
- 3D perspective transforms
- Aspect ratio selection
- Timeline state (duration, playhead, isPlaying, isLooping, tracks, zoom)
- Animation clips (preset-based clips with start time, duration, color)
- Slides (multi-slide support for slideshows)
Manages canvas rendering state:
- Screenshot/image state (for Konva)
- Background mode (solid/gradient)
- Shadow configuration
- Pattern configuration
- Frame configuration
- Canvas dimensions and padding
- Noise configuration
Store Synchronization: EditorStoreSync component keeps both stores in sync using React effects.
The canvas rendering uses a hybrid approach:
- Background Layer - Rendered via HTML/CSS, captured with html2canvas
- User Image Layer - Rendered via Konva Stage
- Overlay Layer - Text and image overlays rendered separately, composited on top
This separation allows:
- High-quality background rendering with CSS effects
- Precise image positioning with Konva
- Proper layering of overlays above user content
The export process follows a multi-step compositing pipeline:
1. Export Background (html2canvas)
├── Clone background element
├── Apply blur effects
└── Apply noise overlay
2. Export Konva Stage (user images)
├── Hide background layer
├── Export at high pixel ratio
└── Scale to export dimensions
3. Export Overlays (html2canvas)
├── Create temporary DOM container
├── Render text overlays with fonts
├── Render image overlays
└── Capture with html2canvas
4. Composite Layers
├── Background (bottom)
├── User Image (middle)
└── Overlays (top)
5. Add Watermark
6. Convert to Blob/DataURL
Images are stored using a hybrid approach:
- Blob URLs - Temporary URLs for uploaded images
- IndexedDB - Persistent storage for:
- Uploaded image blobs (keyed by unique ID)
- Exported images with metadata
- Export preferences
When an image is uploaded:
- A blob URL is created for immediate use
- The blob is saved to IndexedDB with a unique ID
- The ID is stored in canvas objects for persistence
EditorLayout- Main editor container with responsive panelsEditorLeftPanel- Left sidebar with controlsEditorRightPanel- Right sidebar with style optionsEditorBottomBar- Bottom bar with export/actions
EditorCanvas- Wrapper that shows upload UI or canvasClientCanvas- Main Konva canvas renderer (client-only)CanvasContext- Context provider for canvas operations
BorderControls- Border style and configurationShadowControls- Shadow customizationPerspective3DControls- 3D transform controlsBackgroundEffects- Background blur and noise
- File Upload: Uses
react-dropzonefor drag-and-drop - Website Screenshot: API route calls Screen-Shot.xyz service
- Supports desktop (1920x1080) and mobile (375x667) viewport sizes
- Device type selection via UI dropdown
- Screenshots cached separately by device type
- Validation: File type and size validation, URL validation
- Storage: Blob URL creation + IndexedDB persistence
Supports three background types:
- Gradient: CSS linear gradients with customizable colors and angles
- Solid: Single color backgrounds
- Image: Cloudinary-hosted images or uploaded images
Background effects:
- Blur: Applied via CSS filter, captured in export
- Noise: Generated noise texture with overlay blend mode
- Multiple text overlays with independent positioning
- Custom fonts, colors, sizes, weights
- Text shadows with customizable properties
- Vertical/horizontal orientation
- Position stored as percentage for responsive scaling
- Decorative overlays from Cloudinary gallery
- Custom uploaded overlays
- Position, size, rotation, flip controls
- Opacity and visibility toggles
- Scale: Percentage-based scaling
- Opacity: 0-100% opacity control
- Border Radius: Rounded corners
- Borders: Multiple border styles (glassy, window, ruler, etc.)
- Shadows: Customizable shadow with blur, offset, spread, color
- 3D Perspective: CSS 3D transforms with perspective
- Format: PNG (with transparency support)
- Quality: 0-1 quality slider
- Scale: Up to 5x scaling for high-resolution exports
- Watermark: Automatic watermark addition
- Storage: Exported images saved to IndexedDB
Pre-configured design presets that apply:
- Aspect ratio
- Background configuration
- Border and shadow settings
- Image transformations
Presets are defined in lib/constants/presets.ts and can be applied with one click.
The animation system enables keyframe-based animations on canvas properties with real-time preview and video export.
Interpolation (interpolation.ts):
getInterpolatedProperties()- Interpolate values between keyframes at a given timegetClipInterpolatedProperties()- Multi-clip aware interpolation (later clips override earlier)findSurroundingKeyframes()- Locate keyframe context for smooth transitionsclonePresetTracks()- Apply presets with fresh unique IDs
Easing Functions: linear, ease-in, ease-out, ease-in-out, ease-in-cubic, ease-out-cubic, ease-in-expo, ease-out-expo
Animatable Properties: perspective, rotateX, rotateY, rotateZ, translateX, translateY, scale, imageOpacity
20+ presets organized in 5 categories:
| Category | Presets |
|---|---|
| Reveal | Hero Landing, Slide In 3D, Rise & Settle, Drop In |
| Flip | Flip X, Flip Y, Peek |
| Perspective | Showcase Tilt, Isometric, Hover Float, Parallax Drift |
| Orbit | Orbit Left, Orbit Right, Turntable |
| Depth | Push Away, Pull Close, Dramatic Zoom, Breathe 3D |
- TimelineEditor - Main timeline UI with ruler, playhead, and tracks
- TimelineControls - Playback controls (play, pause, skip, loop toggle)
- TimelineTrack - Individual animation track with keyframe markers
- TimelinePlayhead - Draggable vertical line for current position
- KeyframeMarker - Visual keyframe indicators on tracks
- AnimationPresetGallery - Browse and apply animation presets
The playback hook manages the animation loop:
- Runs via
requestAnimationFrameto update the playhead every frame - Applies interpolated properties to the store's
perspective3DandimageOpacity - Supports scrubbing (when paused, updates properties on playhead change)
- Handles automatic slide switching based on playhead position
- Supports looping with modulo math
interface Keyframe {
id: string;
time: number; // milliseconds
properties: Partial<AnimatableProperties>;
easing: EasingFunction;
}
interface AnimationTrack {
id: string;
name: string;
type: 'transform' | 'opacity';
keyframes: Keyframe[];
isLocked: boolean;
isVisible: boolean;
clipId?: string; // Links to animation clip
}
interface AnimationClip {
id: string;
presetId: string;
name: string;
startTime: number; // ms
duration: number; // ms
color: string;
}
interface TimelineState {
duration: number;
playhead: number;
isPlaying: boolean;
isLooping: boolean;
tracks: AnimationTrack[];
zoom: number;
snapToKeyframes: boolean;
}The video export pipeline supports multiple formats and encoders with automatic selection.
User clicks Export Video
↓
Select encoder (based on format + browser support)
↓
For each frame:
1. Set playhead to frame time
2. Apply interpolated animation properties
3. Capture canvas as image
4. Encode frame
↓
Finalize video file
↓
Download + progress tracking
Format=GIF → FFmpeg (only option)
Format=WebM → MediaRecorder (native support)
Format=MP4 → WebCodecs (if available) → FFmpeg (fallback)
| Encoder | File | Use Case |
|---|---|---|
| FFmpeg WASM | ffmpeg-encoder.ts |
H.264/GIF, multi-threaded via SharedArrayBuffer |
| WebCodecs | webcodecs-encoder.ts |
Hardware-accelerated H.264 with mp4-muxer |
| MediaRecorder | video-encoder.ts |
Native WebM via browser API |
| Quality | Bitrate | CRF |
|---|---|---|
| High | 25 Mbps | 18 |
| Medium | 10 Mbps | 23 |
| Low | 5 Mbps | 28 |
- FFmpeg uses JPEG frames (~5x faster than PNG)
- WASM binaries cached via Cache API across sessions
- Streaming frame processing to prevent memory bloat
- Progress tracking via
useExportProgresshook
User uploads image
↓
File validation
↓
Create blob URL
↓
Update useImageStore (uploadedImageUrl)
↓
EditorStoreSync syncs to useEditorStore (screenshot.src)
↓
ClientCanvas renders image on Konva stage
User clicks export
↓
useExport hook called
↓
Get Konva stage reference
↓
exportElement() called with all state
↓
1. Export background (html2canvas)
2. Export Konva stage (user image)
3. Export overlays (html2canvas)
4. Composite all layers
5. Add watermark
6. Convert to blob
↓
Download file + save to IndexedDB
User changes control (e.g., border width)
↓
Control component calls store setter
↓
Zustand store updates
↓
Components subscribed to store re-render
↓
EditorStoreSync syncs changes to editor store
↓
ClientCanvas re-renders with new state
- Konva stage uses
batchDraw()to minimize redraws - Pattern and noise textures are cached
- Background images are loaded once and reused
- Background and overlays exported separately to optimize memory
- High-resolution exports use scaling instead of large canvas dimensions
- Export operations are async to prevent UI blocking
- Cloudinary images use optimized URLs with auto-format and quality
- Images are cached in browser cache
- IndexedDB provides persistent storage for offline access
# Site Configuration
NEXT_PUBLIC_SITE_URL=http://localhost:3000
# Cloudflare R2 Configuration
NEXT_PUBLIC_R2_PUBLIC_URL=https://your-r2-bucket-url
NEXT_PUBLIC_R2_CUSTOM_DOMAIN=your-r2-custom-domain
NEXT_PUBLIC_CDN_URL=https://your-cdn-domain
R2_ACCESS_KEY_ID=your_r2_access_key_id
R2_SECRET_ACCESS_KEY=your_r2_secret_access_key
R2_BUCKET_NAME=betterflow-storage
CLOUDFLARE_ACCOUNT_ID=your_cloudflare_account_id
R2_API_TOKEN=your_r2_api_token
# Optional: Cloudflare D1 Database
CLOUDFLARE_D1_DATABASE_ID=your_d1_database_id
# Optional: Analytics
NEXT_PUBLIC_POSTHOG_KEY=your_posthog_api_key
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com- Method: POST
- Purpose: Generate presigned R2 upload URLs for Chrome extension
- Body:
{ "fileName": "string (required)", "contentType": "string (required)" } - Returns:
{ "uploadUrl": "string", "fileUrl": "string" }
- Method: POST
- Purpose: Direct video upload from Chrome extension to R2
- Body:
{ "videoBase64": "string (required)", "fileName": "string (optional)" } - Returns:
{ "success": true, "fileUrl": "string" }
- Method: GET
- Purpose: Proxy R2 images to avoid CORS issues
- Query:
?url=<encoded-r2-url>
- Method: POST
- Purpose: Clear cached images from R2
- Method: POST
- Purpose: Server-side image export with FFmpeg
-
image-blobs- Stored uploaded images- Key: Unique image ID
- Value: Blob, type, timestamp
-
exports- Stored exported images- Key: Unique export ID
- Value: Blob, format, quality, scale, timestamp, fileName
-
export-preferences- Export settings- Key:
'preferences' - Value: format, quality, scale
- Key:
canvas-objects- Canvas object state (for persistence)canvas-background-prefs- Background preferences
- next (16.0.1) - React framework
- react (19.2.0) - UI library
- konva (10.0.8) - Canvas library
- react-konva (19.2.0) - React bindings for Konva
- zustand (5.0.8) - State management
- html2canvas (1.4.1) - DOM to canvas
- modern-screenshot (4.6.6) - 3D transform capture
- cloudinary (2.8.0) - Image optimization
- radix-ui - UI primitives
- tailwindcss (4) - Styling
- typescript (5) - Type checking
- sharp (0.34.4) - Image processing
- tsx (4.20.6) - TypeScript execution
- File Upload Validation: File type and size validation on client and server
- CORS: Images loaded with
crossOrigin: 'anonymous'for canvas operations - API Keys: Environment variables for sensitive credentials
- XSS Prevention: React's built-in XSS protection
- Content Security: No eval() or dangerous code execution
- Web Workers: Move heavy export operations to web workers
- Service Worker: Cache assets and enable offline functionality
- Virtual Scrolling: For large overlay galleries
- Collaboration: Real-time collaboration with WebSockets
- Cloud Storage: Optional cloud storage for designs
- Audio Support: Add audio tracks to video exports
- Custom Easing Curves: Bezier curve editor for animation easing
- Store logic (Zustand stores)
- Utility functions (export, image processing)
- Component logic (hooks)
- Export pipeline
- Store synchronization
- Image upload flow
- Complete user workflows
- Export functionality
- Cross-browser compatibility
- Serverless functions for API routes
- Edge functions for static assets
- Environment variables configured in Vercel dashboard
npm run build # Next.js production buildvercel.jsonconfigures function timeouts and memory- Screenshot API route has 60s timeout (maxDuration) for external API calls
- Umami Analytics: Privacy-focused analytics integration
- Error Tracking: Error boundaries catch React errors
- Performance: Next.js built-in performance monitoring
See CONTRIBUTING.md for detailed contribution guidelines.