Realtime Collaborative Whiteboard with Next.js 14: Detailed Summary
Key Concepts:
- Realtime collaboration
- Next.js 14
- Convex (backend and database)
- Clerk (authentication)
- Liveblocks (realtime presence and collaboration)
- Shadcn UI (component library)
- Tailwind CSS (styling)
- Server Components
- File and Folder Based Routing
- JWT Templates
- Stripe (for SaaS implementation - mentioned but not covered in detail)
1. Introduction and Project Overview:
- The project is a realtime collaborative whiteboard application, a "Miro clone," built with Next.js 14.
- It allows multiple users to interact with the same content simultaneously, including sticky notes, wireframing, and drawing.
- The goal is to create a digital whiteboard for brainstorming, planning, and team collaboration, regardless of location.
- The tutorial covers the entire development process, from setting up the project to implementing realtime features.
- A SaaS extension with Stripe integration is mentioned as additional content.
2. Features Demonstrated:
- Realtime Sticky Notes: Simulates a sticky note meeting where users can add, move, and update notes in realtime.
- App Wireframing: Enables visual representation of app ideas with drawing, layering, and repositioning of elements.
- Drawing: Allows freeform drawing with various colors and tools for creative brainstorming.
- Organization: Includes "move to back" and "bring to front" features for managing element order.
- Collaboration: Supports favoriting, organization creation, and team member invitations.
3. Technology Stack:
- Next.js 14: React framework for building the application.
- React: JavaScript library for building user interfaces.
- Tailwind CSS: Utility-first CSS framework for styling.
- Shadcn UI: Collection of reusable components for UI elements.
- Convex: Backend and database for realtime data synchronization.
- Liveblocks: Infrastructure for realtime presence and collaboration.
- Clerk: Authentication and user management.
4. Setting Up Next.js 14:
- System Requirements: Verify Node.js version using
node -v. - Automatic Installation: Use
npx create-next-app@latestto create a new Next.js project.- Project name: User-defined (e.g., "board-video-tutorial").
- TypeScript: Yes.
- ESLint: Yes.
- Tailwind CSS: Yes.
- Source directory: No.
- App Router: Yes (crucial for React Server Components).
- Import alias: No (or customize if needed).
- Open Repository: Open the created folder in a code editor (e.g., VS Code).
- Install Shadcn UI: Use
npx shadcn-ui@latest init.- Style: Default (recommended for Lucid icons).
- Color: Slate (or any preferred color).
- CSS variables: Yes.
- Run Development Server: Use
npm run devto start the application onlocalhost:3000.
5. Working with Shadcn UI Components:
- Shadcn UI is not a traditional component library; it's a collection of reusable components that are copied into the project.
- Install a Component: Use
npx shadcn-ui@latest add <component-name>(e.g.,npx shadcn-ui@latest add button). - Component Location: Components are added to the
components/uifolder (e.g.,components/ui/button.tsx). - Import and Use: Import components in pages or other components using
@/components/ui/<component-name>. - Customization: Modify the source code of Shadcn UI components directly to fit the design.
- Variants, sizes, and class names can be customized.
6. Next.js Routing:
- Top-Level Folders:
app: Contains the routing system, React Server Components, and interactive components.public: Stores static assets (images, fonts, etc.).pages(optional): Legacy routing system (not used in this tutorial).src(optional): Source folder (not used in this tutorial).
- Top-Level Files:
next.config.js: Configuration file for Next.js (e.g., extending webpack).package.json: Lists project dependencies and scripts.middleware.ts(optional): Intercepts routes for authentication or bot detection..env.local: Stores environment variables.tailwind.config.js,tsconfig.json,.gitignore: Configuration files.
- Routing Conventions:
page.tsx: Represents a page (route).layout.tsx: Represents a layout that wraps a page or route segment.loading.tsx: Represents a loading state.error.tsx: Represents an error state.route.ts: Used to create API endpoints.
- Creating New Routes:
- Create a new folder inside the
appfolder. - Add a
page.tsxfile inside the new folder. - Export a default React component from
page.tsx.
- Create a new folder inside the
- Nested Routes: Create nested folders to create nested routes (e.g.,
app/test/subroute/page.tsxcreates the route/test/subroute). - Dynamic Route Segments: Use square brackets to create dynamic route segments (e.g.,
app/users/[userId]/page.tsxcreates the route/users/123).- Access dynamic parameters using the
paramsprop in the page component.
- Access dynamic parameters using the
- Route Groups: Use parentheses to group routes without affecting the URL structure (e.g.,
app/(test)/subroute/page.tsxcreates the route/subroute). - Omitting Folders from Routing: Use underscores to completely exclude a folder and its children from routing (e.g.,
app/_components/ui/button.tsxwill not create a route). - Layouts:
- Create a
layout.tsxfile inside a folder. - Export a default React component that accepts a
childrenprop. - Render the
childrenprop within the layout component. - Layouts are reusable and persist across route changes within their scope.
- Create a
7. Setting Up Convex:
- Create a Convex Account: Sign up at
convex.dev. - Install Convex Package: Use
npm install convex. - Initialize Convex: Use
npx convex dev.- This command prompts for GitHub login, project creation, and saves production and deployment URLs.
- It also creates a
convexfolder for backend API functions.
- Run Convex Backend: Use
npx convex devin a separate terminal. - Run Frontend: Use
npm run devin another terminal. - Convex Dashboard: Access the Convex dashboard to view data, tables, and logs.
8. Setting Up Clerk:
- Create a Clerk Account: Sign up at
clerk.com. - Create a New Application:
- Application name: User-defined (e.g., "board").
- Select "Email address" as a supported authentication method (required for organization invites).
- Optionally allow Google or other authentication methods.
- Add Environment Variables: Copy the
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYandCLERK_SECRET_KEYfrom the Clerk dashboard to the.env.localfile. - Install Clerk Next.js Package: Use
npm install @clerk/nextjs. - Create a Middleware File: Create
middleware.tsto protect the application with authentication.- Copy the middleware code from the Clerk documentation.
- Create a JWT Template:
- Go to "JWT Templates" in the Clerk dashboard.
- Create a new template for "Convex."
- Ensure the "AUD" claim is set to "convex."
- Add "organization role" and "organization ID" to the JWT template.
- Copy the "Issuer URL" from the JWT template.
- Configure Convex Authentication:
- Create a file named
auth.config.jsinside theconvexfolder. - Copy the code from the Convex documentation for Clerk authentication.
- Replace the dummy domain with the "Issuer URL" from the Clerk JWT template.
- Create a file named
9. Creating a Universal Provider (Convex and Clerk):
- Create a Providers Folder: Create a folder named
providersin the root of the application. - Create a Client Provider: Create a file named
convex-client-provider.tsxinside theprovidersfolder. - Mark as Client Component: Add
"use client"at the top of the file. - Import Necessary Modules:
ClerkProvideranduseAuthfrom@clerk/nextjs.ConvexProviderWithClerk,useAuth,AuthLoading,Authenticated, andConvexReactClientfromconvex/react-clerk.
- Create a Convex Client:
- Get the Convex URL from
process.env.NEXT_PUBLIC_CONVEX_URL. - Create a new
ConvexReactClientinstance with the Convex URL.
- Get the Convex URL from
- Create a Provider Component:
- Create a React component named
ConvexClientProviderthat accepts achildrenprop. - Wrap the
childrenwithClerkProviderandConvexProviderWithClerk. - Pass
useAuthtoConvexProviderWithClerkas theuseAuthprop. - Pass the Convex client instance to
ConvexProviderWithClerkas theclientprop.
- Create a React component named
- Wrap the Application:
- Go to
app/layout.tsx. - Wrap the
childrenprop inside thebodytag with theConvexClientProvidercomponent.
- Go to
10. Adding a Loading State and Logout:
- Create a Loading Component:
- Create a folder named
outinside thecomponentsfolder. - Create a file named
loading.tsxinside theoutfolder. - Create a React component that displays a loading indicator (e.g., a logo with a pulsing animation).
- Create a folder named
- Modify the Client Provider:
- In
convex-client-provider.tsx, use theAuthLoadingandAuthenticatedcomponents fromconvex/react-clerk. - Render the loading component when
AuthLoadingis true. - Render the
childrenprop only whenAuthenticatedis true.
- In
- Add a User Button:
- In
app/page.tsx, add theUserButtoncomponent from@clerk/nextjs. - This component provides a user interface for managing the user's account and logging out.
- In
11. Project Layout and Structure:
- Dashboard Route Group: Move the root
page.tsxinside a route group folder nameddashboard(e.g.,app/dashboard/page.tsx). - Dashboard Layout: Create a
layout.tsxfile inside thedashboardfolder to define a reusable layout for the dashboard. - Sidebar Component: Create a
sidebarcomponent inside thecomponentsfolder. - Organization Sidebar Component: Create an
organization-sidebarcomponent inside thecomponentsfolder. - Navbar Component: Create a
navbarcomponent inside thecomponentsfolder. - Layout Structure:
- Use a
mainelement to wrap the entire layout. - Render the
sidebarcomponent inside themainelement. - Use a
flexcontainer to position theorganization-sidebar,navbar, and content area.
- Use a
12. Implementing Organization Management:
- Enable Organizations in Clerk: Go to the Clerk dashboard, select the application, and enable organizations.
- Update JWT Template: Add "organization role" and "organization ID" to the Convex JWT template in Clerk.
- Install Dialogue and Tooltip Components: Use
npx shadcn-ui@latest add dialog tooltip. - New Button Component: Create a
new-buttoncomponent inside thesidebarfolder to trigger the organization creation dialogue. - List Component: Create a
listcomponent inside thesidebarfolder to display a list of organizations. - Item Component: Create an
itemcomponent inside thesidebarfolder to represent an individual organization in the list. - Hint Component: Create a reusable
hintcomponent to display tooltips on hover. - Organization Switcher: Use the
OrganizationSwitchercomponent from@clerk/nextjsto allow users to switch between organizations. - Invitation Functionality: Use the Clerk organization features to invite team members to organizations.
13. Implementing Search and Favorite Boards:
- Add Search Input: Create a
search-inputcomponent to allow users to search for boards. - Use Query String Package: Install
query-stringanduse-hooks-tsfor managing URL parameters. - Implement Search Logic: Use
useState,useDebounce, anduseRouterto update the URL with the search query. - Create Empty States: Create separate components for empty search results, empty favorite boards, and empty board lists.
- Board List Component: Create a
board-listcomponent to display a list of boards, handle empty states, and pass search parameters. - Board Card Component: Create a
board-cardcomponent to represent an individual board in the list. - Favorite Boards: Add a "favorites" query parameter to the URL to filter the board list.
- Dynamic Variant: Use the
useSearchParamshook to dynamically change the variant of the "Team boards" and "Favorite boards" buttons based on the URL.
14. Implementing Board Actions (Rename, Delete, Copy Link):
- Install Dropdown Menu and Alert Dialogue Components: Use
npx shadcn-ui@latest add dropdown-menu alert-dialog. - Create Actions Component: Create a reusable
actionscomponent to display a dropdown menu with options to copy the board link, delete the board, and rename the board. - Copy Board Link: Implement the
navigator.clipboard.writeTextAPI to copy the board link to the clipboard. - Delete Board: Create a Convex mutation to delete a board from the database.
- Confirm Model Component: Create a reusable
confirm-modelcomponent to display a confirmation dialogue before deleting a board. - Rename Board: Create a Convex mutation to update the title of a board.
- Use Rename Model Hook: Create a
useRenameModelhook usingzustandto manage the state of the rename dialogue.
15. Implementing Favoriting Functionality:
- Extend Database Schema: Add a
userFavoritestable to the Convex schema to store user-board relationships. - Create Favorite and Unfavorite Mutations: Create Convex mutations to add and remove boards from the user's favorites.
- Modify Board Card Component: Add a button to the
board-cardcomponent to toggle the favorite status of a board. - Update Board Query: Modify the Convex query to include a boolean field indicating whether a board is favorited by the current user.
- Dynamic Class Name: Use a dynamic class name to change the appearance of the favorite button based on the board's favorite status.
16. Implementing Search and Favorite Queries:
- Modify Convex Boards Function: Add optional
searchandfavoritesparameters to the Convex query for boards. - Implement Search Index: Use the Convex search index to filter boards based on the search query.
- Implement Favorite Query: Use the
userFavoritestable to filter boards based on the user's favorite boards. - Conditional Rendering: Conditionally render the appropriate empty state component based on the search query and favorite status.
17. Implementing Board Creation and Redirection:
- Modify Empty Boards Component: Add a button to the
empty-boardscomponent to create a new board. - Use Router Hook: Use the
useRouterhook fromnext/navigationto redirect the user to the newly created board. - Modify New Board Button Component: Add the same redirection logic to the
new-board-buttoncomponent.
18. Creating the Board View (Canvas):
- Create a Board Route: Create a new folder named
boardin theappfolder, and inside create a dynamic route segment[boardId]with apage.tsxfile. - Create Canvas Component: Create a
canvascomponent to represent the whiteboard canvas. - Create Info Component: Create an
infocomponent to display information about the board. - Create Participants Component: Create a
participantscomponent to display a list of users in the board. - Create Toolbar Component: Create a
toolbarcomponent to display the drawing tools and actions. - Basic Layout: Position the
info,participants, andtoolbarcomponents around thecanvascomponent.
19. Connecting to Liveblocks:
- Install Liveblocks Packages: Use
npm install @liveblocks/client @liveblocks/react. - Initialize Liveblocks: Use
npx create-liveblocks-app@latest --init --framework reactto create aliveblocks.config.tsfile. - Configure Liveblocks:
- Replace the public API key with the one from the Liveblocks dashboard.
- Add the authentication endpoint.
- Create a Room Component: Create a
roomcomponent to wrap thecanvascomponent and provide realtime collaboration features. - Room Provider: Use the
RoomProvidercomponent from@liveblocks/reactto connect to a Liveblocks room. - Client-Side Suspense: Use the
ClientSideSuspensecomponent from@liveblocks/reactto handle loading states.
20. Implementing Authentication with Liveblocks:
- Install Liveblocks Node Package: Use
npm install @liveblocks/node. - Create Authentication Endpoint: Create a route handler at
app/api/liveblocks-auth/route.tsto authenticate users. - Initialize Liveblocks and Convex: Initialize Liveblocks and Convex instances in the route handler.
- Authenticate User: Use Clerk's
authandcurrentUserfunctions to authenticate the user. - Check Organization Membership: Verify that the user is a member of the organization associated with the board.
- Prepare Session: Use
liveblocks.prepareSessionto create a Liveblocks session. - Allow Room Access: Use
session.allowRoomto grant the user access to the Liveblocks room. - Configure Liveblocks Client: Update the
liveblocks.config.tsfile to use the authentication endpoint.
21. Implementing Realtime Cursors:
- Extend Liveblocks Configuration: Add
cursorto thepresencetype inliveblocks.config.ts. - Create Cursors Presence Component: Create a
cursors-presencecomponent to render other users' cursors. - Use UseOthers Hook: Use the
useOthershook from@liveblocks/reactto get a list of other users in the room. - Create Cursor Component: Create a
cursorcomponent to represent an individual user's cursor. - Render Foreign Object: Use a
foreignObjectelement to render the cursor icon and user's name inside the SVG canvas. - Update Presence: Use the
useMutationhook to update the user's presence with their cursor coordinates. - Pointer Event to Canvas Point: Create a utility function to convert pointer event coordinates to canvas coordinates.
- On Pointer Move Event: Add an
onPointerMoveevent handler to the SVG canvas to update the user's presence with their cursor coordinates. - On Pointer Leave Event: Add an
onPointerLeaveevent handler to the SVG canvas to reset the user's cursor presence when they leave the canvas.
22. Implementing Drawing Functionality (Pencil Tool):
- Extend Liveblocks Configuration: Add
pencilDraftandpenColorto thepresencetype inliveblocks.config.ts. - Create Start Drawing Mutation: Create a Convex mutation to initialize the pencil draft with the starting point.
- Create Continue Drawing Mutation: Create a Convex mutation to update the pencil draft with new points.
- Create Insert Path Mutation: Create a Convex mutation to create a new path layer from the pencil draft.
- Create Penpoints to Path Layer Util: Create a utility function to convert an array of points to a path layer.
- Create Path Component: Create a
pathcomponent to render the path layer using thegetStrokefunction fromperfect-freehand. - Update On Pointer Down Event: Add a case to the
onPointerDownevent handler to initialize the pencil draft when the pencil tool is selected. - Update On Pointer Move Event: Add a case to the
onPointerMoveevent handler to update the pencil draft with new points. - Update On Pointer Up Event: Add a case to the
onPointerUpevent handler to create a new path layer from the pencil draft. - Render Drafts: Render the pencil drafts of other users in the
cursors-presencecomponent.
23. Implementing Basic Shape Functionality (Rectangle, Ellipse, Text, Note):
- Create Layer Types: Define enums for the different layer types (rectangle, ellipse, text, note).
- Create Layer Components: Create React components for each layer type (rectangle, ellipse, text, note).
- Update Layer Preview Component: Add cases to the
layer-previewcomponent to render the appropriate component for each layer type. - Create Insert Layer Mutation: Create a Convex mutation to insert a new layer into the database.
- Update On Pointer Up Event: Add a case to the
onPointerUpevent handler to call theinsertLayermutation when a shape tool is selected.
24. Implementing Selection and Transformation:
- Create Use Selection Bounds Hook: Create a hook to calculate the bounding box of the selected layers.
- Create Selection Box Component: Create a
selection-boxcomponent to display a selection box around the selected layers. - Implement Resizing Functionality:
- Add
onResizeHandlePointerDownevent to the selection box handles. - Create a
resizeBoundsutility function to calculate the new bounds of the selected layers. - Create a
resizeSelectedLayermutation to update the dimensions of the selected layers.
- Add
- Implement Translating Functionality:
- Add a
translateSelectedLayersmutation to update the position of the selected layers. - Update the
onPointerMoveevent handler to call thetranslateSelectedLayersmutation when the user is dragging the selected layers.
- Add a
- Implement Deselection Functionality:
- Add an
onPointerDownevent handler to the SVG canvas to deselect the layers when the user clicks outside of the selected layers. - Create an
unselectLayersmutation to clear the selection.
- Add an
25. Additional Tips and Enhancements:
- Use Disable Scroll Bounce Hook: Create a hook to disable scroll bouncing on iOS devices.
- Implement Keyboard Shortcuts: Add keyboard shortcuts for common actions (e.g., Ctrl+Z for undo, Ctrl+Shift+Z for redo, Backspace for delete).
- Replace Two Array with Two Immutable: Refactor the code to use
twoImmutableinstead oftwoArray(recommended by the Liveblocks team).
26. Deployment to Vercel:
- Build the Project: Use
npm run buildto build the project for production. - Create a GitHub Repository: Create a new repository on GitHub.
- Push the Code to GitHub: Push the local code to the new GitHub repository.
- Deploy to Vercel:
- Go to
vercel.com/new. - Select the GitHub repository.
- Override the build command with
npx convex deploy --cmd npm run build. - Add environment variables from
.env.localto Vercel. - Generate a production deploy key in the Convex dashboard and add it to Vercel.
- Deploy the application.
- Go to
27. Conclusion:
- The tutorial provides a comprehensive guide to building a realtime collaborative whiteboard application with Next.js 14, Convex, Clerk, and Liveblocks.
- It covers a wide range of topics, including setting up the project, implementing realtime features, managing user authentication, and deploying the application.
- The resulting application is a powerful tool for brainstorming, planning, and team collaboration.
AI summaries can miss context or contain errors. Check important details against the original video.