Build a YouTube Clone with Next.js 15: React, Tailwind, Drizzle, tRPC (Part 2/2)

Code With AntonioAbout 10 min readMay 27, 2025Watch original
THE SUMMARYAI-generated

YouTube Clone - Part 2 & Beyond: Detailed Summary

Key Concepts:

  • Video Page Structure: Video section, suggestions, comments.
  • Drizzle ORM: Inner joins, get table columns, Common Table Expressions (CTE), database.count, SQL operator.
  • Next.js App Router: Dynamic routes, prefetching, server components, client components, suspense.
  • TRPC: Public procedures, protected procedures, mutations, useSuspenseQuery, useInfiniteQuery, useMutation, useUtils, invalidation.
  • Clerk: User authentication, useUser, openSignIn, isLoaded.
  • UI Components: Reusability, variants, skeletons, tooltips, dropdown menus, buttons, avatars, text areas.
  • Max: Video hosting, webhooks, playback IDs, statuses.
  • Subscriptions: Viewer/Creator relationship, composite primary keys.
  • Comments: Parent-child relationships, recursive rendering.
  • Infinite Loading: Cursors, limits, hasNextPage, fetchNextPage.
  • Code Organization: Modules, components, types, procedures.
  • Deployment: Vercel, environment variables, webhooks.
  • Optimization: Caching, prefetching, SQL queries.

1. Video Page Structure and Initial Setup:

  • The video page consists of three sections: the video section (player, author info, subscription, reactions), suggestions, and comments.
  • Suggestions and comments are initially placeholders due to missing APIs.
  • The goal is to create a getOne procedure for videos, including author information via an inner join.
  • Dynamic routing is used to create video pages based on video ID (/videos/[videoID]).
  • Prefetching is implemented using videos.getOne.prefetch to load video data on the server.
  • A VideoView component is created to hydrate the client with the prefetched data.
  • A VideoSection component is created to house the video player and related information.
  • The video player component is reused from the form section, passing playbackId and thumbnailUrl.
  • A VideoBanner component is added to display a processing message if the video is not ready.

2. Developing the getOne Procedure with Inner Joins:

  • A public procedure getOne is created in the videos module to load a single video by ID.
  • An inner join is used to load the author's information (image, name, subscriber count) from the users table.
  • get table columns is used to spread the schema for both videos and users in the select statement.
  • A TRPC error with code not found is returned if the video does not exist.
  • The inner join combines data from the videos and users tables in a single SQL query.

3. Prefetching and Hydration:

  • The page.tsx file uses videos.getOne.prefetch to prefetch video data on the server.
  • The VideoView component uses hydrateClient to hydrate the client with the prefetched data.
  • The VideoSection component uses videos.getOne.useSuspenseQuery to fetch the video data on the client, leveraging the prefetching.

4. UI Components and Styling:

  • The video section is styled using Tailwind CSS classes for layout, responsiveness, and appearance.
  • The VideoBanner component displays a processing message if the video is not ready.
  • The VideoTopRow component is created to house the video title, owner information, reactions, and menu.
  • The VideoOwner component displays the author's avatar, name, and subscriber count.
  • The UserAvatar component is reused from the studio sidebar component.
  • A SubscriptionButton component is created to handle subscribing and unsubscribing to the author.
  • A UserInfo component is created to display user information with a tooltip.
  • VideoReactions and VideoMenu components are created as placeholders for future implementation.
  • The VideoDescription component displays the video description with an expansion box.

5. Video Views Implementation:

  • A video_views schema is created with foreign keys to users and videos tables.
  • A composite primary key is created using user_id and video_id.
  • Relations are established between video_views, users, and videos tables.
  • A protected procedure create is created in the video_views module to record a video view.
  • The getOne procedure in the videos module is modified to include a subquery using database.count to count the number of views for a video.
  • The VideoSection component uses videoViews.create.useMutation to trigger the view creation when the video is played.
  • utils.videos.getOne.invalidate is used to refetch the video data after a view is created.

6. Video Reactions Implementation:

  • A video_reactions schema is created with foreign keys to users and videos tables, and a reaction_type enum (like/dislike).
  • A composite primary key is created using user_id and video_id.
  • Relations are established between video_reactions, users, and videos tables.
  • The getOne procedure in the videos module is modified to include subqueries using database.count to count the number of likes and dislikes for a video.
  • A Common Table Expression (CTE) called viewer_reactions is created to determine the current user's reaction to the video.
  • The VideoReactions component is updated to display the number of likes and dislikes, and to highlight the user's reaction.
  • like and dislike procedures are created in the video_reactions module to handle liking and disliking videos.
  • The onConflictDoUpdate method is used to handle cases where the user has already reacted to the video.
  • The VideoReactions component uses videoReactions.like.useMutation and videoReactions.dislike.useMutation to trigger the like and dislike procedures.
  • utils.videos.getOne.invalidate is used to refetch the video data after a like or dislike is created or removed.

7. Subscriptions Implementation:

  • A subscriptions schema is created with foreign keys to users tables for both viewer and creator.
  • A composite primary key is created using viewer_id and creator_id.
  • Relations are established between subscriptions and users tables.
  • A create and remove procedure are created in the subscriptions module to handle subscribing and unsubscribing to users.
  • The getOne procedure in the videos module is modified to include a CTE called viewer_subscriptions to determine if the current user is subscribed to the author of the video.
  • The VideoOwner component is updated to display a "Subscribe" or "Unsubscribe" button based on the user's subscription status.
  • A useSubscription hook is created to encapsulate the logic for subscribing and unsubscribing to users.
  • The SubscriptionButton component uses the useSubscription hook to handle the button's click event.
  • utils.videos.getOne.invalidate is used to refetch the video data after a subscription is created or removed.

8. Comments Implementation:

  • A comments schema is created with foreign keys to users and videos tables, and a value field for the comment text.
  • Relations are established between comments, users, and videos tables.
  • A protected procedure create is created in the comments module to add a new comment.
  • A base procedure getMany is created in the comments module to load comments for a video.
  • The VideoView component is updated to include a CommentSection component.
  • The CommentSection component uses comments.getMany.useSuspenseQuery to fetch the comments for a video.
  • A CommentForm component is created to allow users to add comments.
  • A CommentItem component is created to display individual comments.
  • The getMany procedure is modified to include an inner join to load the author's information for each comment.

9. Infinite Loading for Comments:

  • The getMany procedure in the comments module is modified to support infinite loading using a cursor and a limit.
  • The CommentSection component is updated to use comments.getMany.useSuspenseInfiniteQuery to fetch comments in batches.
  • An InfiniteScroll component is added to the CommentSection to trigger the loading of more comments when the user scrolls to the bottom of the list.
  • A separate query is used to count the total number of comments for a video.
  • A remove procedure is created in the comments module to delete a comment.
  • The CommentItem component is updated to include a delete button that triggers the remove procedure.

10. Video Reactions for Comments:

  • A comment_reactions schema is created with foreign keys to users and comments tables, and a reaction_type enum (like/dislike).
  • A composite primary key is created using user_id and comment_id.
  • Relations are established between comment_reactions, users, and comments tables.
  • The getMany procedure in the comments module is modified to include subqueries using database.count to count the number of likes and dislikes for a comment.
  • A Common Table Expression (CTE) called viewer_reactions is created to determine the current user's reaction to the comment.
  • The CommentItem component is updated to display the number of likes and dislikes, and to highlight the user's reaction.
  • like and dislike procedures are created in the comment_reactions module to handle liking and disliking comments.
  • The onConflictDoUpdate method is used to handle cases where the user has already reacted to the comment.
  • The CommentItem component uses commentReactions.like.useMutation and commentReactions.dislike.useMutation to trigger the like and dislike procedures.
  • utils.comments.getMany.invalidate is used to refetch the comment data after a like or dislike is created or removed.

11. Comment Replies Implementation:

  • A parent_id foreign key is added to the comments schema to represent the parent-child relationship between comments and replies.
  • The CommentItem component is updated to include a "Reply" button that opens a comment form.
  • A CommentForm component is created to allow users to add replies.
  • A CommentReplies component is created to display the replies for a comment.
  • The getMany procedure in the comments module is modified to support loading replies for a specific comment using the parent_id parameter.
  • The CommentReplies component uses comments.getMany.useInfiniteQuery to fetch replies in batches.

12. Suggestions Implementation:

  • A suggestions module is created with a getMany procedure to load suggested videos.
  • The getMany procedure loads videos with the same category as the current video, excluding the current video itself.
  • The VideoView component is updated to include a SuggestionsSection component.
  • The SuggestionsSection component uses suggestions.getMany.useSuspenseInfiniteQuery to fetch suggestions in batches.
  • VideoRowCard and VideoGridCard components are created to display the suggestions in different layouts.

13. Search Page Implementation:

  • A search page is created with a dynamic route (/search).
  • The getMany procedure in the videos module is modified to support searching for videos by title and category.
  • The SearchInput component is updated to redirect to the search page with the search query and category.
  • The SearchPage component uses videos.getMany.useSuspenseInfiniteQuery to fetch search results in batches.
  • VideoRowCard and VideoGridCard components are used to display the search results in different layouts.

14. Subscriptions List Implementation:

  • A getManySubscribed procedure is created in the videos module to load videos from users that the current user is subscribed to.
  • A subscriptions page is created with a dynamic route (/subscriptions).
  • The SubscriptionsPage component uses videos.getManySubscribed.useSuspenseInfiniteQuery to fetch videos from subscribed users in batches.
  • VideoRowCard and VideoGridCard components are used to display the videos in different layouts.

15. User Page Implementation:

  • A users module is created with a getOne procedure to load user information.
  • A dynamic route is created for user pages (/users/[userID]).
  • The UserPage component uses users.getOne.useSuspenseQuery to fetch user data.
  • A UserSection component is created to display user information, including the avatar, name, subscriber count, and a "Subscribe" or "Edit Profile" button.
  • The getOne procedure is modified to include the number of videos and subscribers for the user.
  • The getMany procedure in the videos module is modified to accept a userID parameter, allowing the loading of videos for a specific user.

16. Manual Video Revalidation:

  • A revalidate procedure is created in the videos module to manually revalidate a video's status and playback ID.
  • The FormSection component in the studio is updated to include a "Revalidate" button that triggers the revalidate procedure.

17. Banner Upload Implementation:

  • A bannerUploader is implemented in uploadthing.config.ts to handle banner image uploads.
  • The users schema is updated to include bannerURL and bannerKey fields.
  • A BannerUploadModel component is created to allow users to upload a banner image.
  • The UserPageBanner component is updated to display the banner image or a placeholder if no banner is uploaded.

18. Final Improvements and Deployment:

  • The responsive navbar is fixed.
  • The useToast import is removed from the useToast hook.
  • export const dynamic = 'force-dynamic' is added to all relevant pages.
  • The project is pushed to GitHub.
  • The project is deployed to Vercel.
  • Environment variables are configured on Vercel.
  • Webhooks are configured on Clerk and Mux.

Main Takeaways:

  • This project demonstrates a comprehensive approach to building a full-stack web application using Next.js, TRPC, Drizzle ORM, Clerk, and Mux.
  • The use of server components, prefetching, and caching techniques results in a highly performant application.
  • The modular code organization and reusable UI components promote maintainability and scalability.
  • The project showcases advanced SQL querying techniques using inner joins, subqueries, and Common Table Expressions.
  • The implementation of various features, such as video views, reactions, subscriptions, comments, and playlists, provides a rich user experience.
  • The project emphasizes the importance of data validation, error handling, and security considerations.
  • The deployment process highlights the steps involved in configuring environment variables and webhooks for a production environment.

AI summaries can miss context or contain errors. Check important details against the original video.

Go a little deeper.

Have a question about this video? Load its transcript to open the video chat.