A comprehensive guide to building a full-featured YouTube clone application.
πΊ Watch the Full Tutorial @CodeWithAntonio
- π¬ Video infrastructure & storage (powered by MuxHQ)
- π Automatic video transcription
- πΌοΈ Smart thumbnail generation
- π€ AI-powered background jobs (using Upstash)
- π Creator Studio with analytics
- ποΈ Playlist management system
- π¬ Interactive comments
- π Like and subscription system
- π― Watch history tracking
- π User authentication (powered by Better Auth)
- π Next.js 15
- βοΈ React 19
- π tRPC for type-safe APIs
- ποΈ PostgreSQL (Neon Database)
- π DrizzleORM
- π TailwindCSS
- π¨ shadcn/ui
- π± Responsive design
-
Configure environment
- runtime (Node.js, Bun)
- package manager (npm, pnpm, bun)
-
Why Bun?
- Easily run TypeScript scripts with ES6 imports
- Less issues with dependency issues regarding React 19
- Establish basic Bun commands
- bun add === npm install
- bunx === npx
- Create a Postgres database(neon)
- Setup Drizzle ORM
- Create users schema
- Migrate changes to database
- Learn how to use drizzle-kit
- Only ORM with both relational and SQL-Like query APIs
- Serverless by default
- Forcing us to 'understand' our queries
const result = await db.query.users.findMany({
with: {
posts: true,
},
});
const result = await db
.select()
.from(countries)
.leftJoin(cities, eq(cities.countryId, countries.id))
.where(eq(cities.id, 1));
- Create ngrok account (or any other local tunnel solution)
- Obtain a static domain
- Add script to concurrently run local tunnel & app
- Create the users webhook
- end-to-end typesafety
- familiar hooks(useQuery, useMutation, useInfiniteQuery)
- v11 allows us to do authenticated prefetching
- not possible to prefetch authenticated queries
The main limitation of Hono.js in this context is the inability to prefetch authenticated queries. Here's why this matters:
// tRPC approach
// Server components can directly access auth state
async function ProtectedPage() {
// Can prefetch authenticated data directly on the server
const userData = await trpc.auth.getUser.prefetch();
return <Component data={userData} />;
}
// Hono + React Query approach
// β Can't use in server components
("use client");
function ProtectedPage() {
// Auth queries can only happen on the client
const { data } = useQuery({
queryKey: ["user"],
queryFn: () => client.getUser(),
});
}
- Additional network roundtrips required
- Waterfall data loading pattern
- Increased time to first meaningful paint
- Loading flickers and content delays
Modern data fetching pattern that starts loading data before rendering:
// Traditional way (fetch-on-render)
function OldComponent() {
const [data, setData] = useState(null);
useEffect(() => {
// β Only starts fetching after render
fetchData().then(setData);
}, []);
}
// Render as you fetch (with tRPC)
async function NewComponent() {
// β
Start fetching immediately
const dataPromise = trpc.data.query.prefetch();
return (
<Suspense fallback={<Loading />}>
<AsyncContent promise={dataPromise} />
</Suspense>
);
}
React Server Components act as efficient data loaders:
// Server Component as data loader
async function BlogPostLoader({ id }: { id: string }) {
// β
Load data directly on the server
const post = await trpc.posts.getPost.fetch({ id });
const comments = await trpc.comments.list.fetch({ postId: id });
return (
<article>
<PostContent post={post} />
<Suspense fallback={<CommentsSkeleton />}>
<Comments initialData={comments} />
</Suspense>
</article>
);
}
Achieve optimal performance through parallel data fetching:
async function DashboardPage() {
// β
Fetch multiple data sources in parallel
const [userData, posts, analytics, notifications] = await Promise.all([
trpc.users.getProfile.prefetch(),
trpc.posts.list.prefetch(),
trpc.analytics.summary.prefetch(),
trpc.notifications.recent.prefetch(),
]);
return (
<Layout>
<UserProfile data={userData} />
<RecentPosts posts={posts} />
<AnalyticsDashboard data={analytics} />
<NotificationPanel notifications={notifications} />
</Layout>
);
}
-
Performance
- Reduced total loading time
- Avoided serial requests
- Optimized first paint
-
User Experience
- Progressive loading
- Faster interaction response
- Smoother page transitions
-
Developer Experience
- Declarative data fetching
- Type safety
- Simplified error handling
-
Resource Utilization
- Reduced server load
- Optimized bandwidth usage
- Better cache utilization
- Enable transformer on tRPC
- Add auth to tRPC context
- Add protected procedure
- Add rate limiting
- Create categories schema
- Push changes to the database
- Seed categories
- Prefetch categories
- Create categories component
- Create studio route group
- Create studio layout
- Protect studio routes
why background jobs?
- avoid timeout from long-running tasks
- problematic with AI generations
- ensure retries in case of failure