feat: optimize bundle size and improve image loading

- Unify video page components into single VideoPageContent component
  - Reduces ~2300 lines of duplicated code between dashboard and watch pages
  - Uses mode prop ('dashboard' | 'watch') to handle differences

- Move shadcn to devDependencies (CLI tool, not needed at runtime)

- Remove radix-ui meta-package (components already imported individually)

- Replace <img> tags with next/image for automatic optimization
  - Add remotePatterns config for YouTube, Vimeo, Unsplash domains
  - Update video-card.tsx, videos/new/page.tsx, component-example.tsx

- Add next/dynamic for KeyboardShortcutsModal (lazy load on demand)
This commit is contained in:
Yusuf İpek
2026-02-10 14:08:52 +03:00
parent 062a0aec83
commit b22fd1476f
9 changed files with 2364 additions and 4429 deletions
File diff suppressed because it is too large Load Diff
@@ -3,6 +3,7 @@
import { useState, useEffect } from 'react';
import { useRouter, useParams } from 'next/navigation';
import Link from 'next/link';
import Image from 'next/image';
import { ArrowLeft, Loader2, Link as LinkIcon, AlertCircle, CheckCircle2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
@@ -177,10 +178,12 @@ export default function NewVideoPage() {
<div className="space-y-2">
<Label>Preview</Label>
<div className="relative aspect-video rounded-lg overflow-hidden bg-muted">
<img
<Image
src={thumbnailUrl}
alt="Video thumbnail"
className="object-cover w-full h-full"
fill
sizes="(max-width: 768px) 100vw, 600px"
className="object-cover"
/>
</div>
</div>
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -1,6 +1,7 @@
"use client"
import * as React from "react"
import Image from "next/image"
import {
Example,
@@ -81,10 +82,12 @@ function CardExample() {
<Example title="Card" className="items-center justify-center">
<Card className="relative w-full max-w-sm overflow-hidden pt-0">
<div className="bg-primary absolute inset-0 z-30 aspect-video opacity-50 mix-blend-color" />
<img
<Image
src="https://images.unsplash.com/photo-1604076850742-4c7221f3101b?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="Photo by mymind on Unsplash"
title="Photo by mymind on Unsplash"
width={400}
height={225}
className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale"
/>
<CardHeader>
+6 -1
View File
@@ -2,6 +2,7 @@
import { useState } from 'react';
import Link from 'next/link';
import dynamic from 'next/dynamic';
import { usePathname } from 'next/navigation';
import {
Video,
@@ -25,9 +26,13 @@ import {
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
import { ThemeToggle } from '@/components/theme-toggle';
import { KeyboardShortcutsModal } from '@/components/keyboard-shortcuts-modal';
import { cn } from '@/lib/utils';
const KeyboardShortcutsModal = dynamic(
() => import('@/components/keyboard-shortcuts-modal').then(mod => mod.KeyboardShortcutsModal),
{ ssr: false }
);
interface NavItem {
href: string;
label: string;
+5 -2
View File
@@ -2,6 +2,7 @@
import { useState } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import {
Play,
@@ -178,10 +179,12 @@ export function VideoCard({ video, projectId }: VideoCardProps) {
<Link href={`/projects/${projectId}/videos/${video.id}`}>
{/* Thumbnail */}
<div className="relative aspect-video bg-muted overflow-hidden">
<img
<Image
src={video.thumbnailUrl}
alt={video.title}
className="object-cover w-full h-full transition-transform group-hover:scale-105"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover transition-transform group-hover:scale-105"
/>
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
<Play className="h-12 w-12 text-white" fill="white" />
File diff suppressed because it is too large Load Diff
+9 -1
View File
@@ -1,7 +1,15 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'img.youtube.com' },
{ protocol: 'https', hostname: 'i.ytimg.com' },
{ protocol: 'https', hostname: 'i.vimeocdn.com' },
{ protocol: 'https', hostname: 'vumbnail.com' },
{ protocol: 'https', hostname: 'images.unsplash.com' },
],
},
};
export default nextConfig;
+1 -2
View File
@@ -35,10 +35,8 @@
"nodemailer": "^8.0.1",
"pg": "^8.18.0",
"prisma": "^7.3.0",
"radix-ui": "^1.4.3",
"react": "19.2.3",
"react-dom": "19.2.3",
"shadcn": "^3.8.3",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
"tw-animate-css": "^1.4.0",
@@ -54,6 +52,7 @@
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.1.6",
"shadcn": "^3.8.3",
"tailwindcss": "^4",
"typescript": "^5"
},