DEV Community

Cover image for Build an F1 Dashboard with Real-Time Collaboration Using v0 and Velt🔥
Astrodevil
Astrodevil

Posted on

Build an F1 Dashboard with Real-Time Collaboration Using v0 and Velt🔥

F1 thrives on leaderboards and data. Fans and teams need to know who’s leading and by how many points.

However, most F1 dashboards are pretty much static. The most they can do is to update records and points in real time: no collaboration or something of that sort. There’s no way for fans or teams to collaborate.

F1 scoreboard

Modern F1 dashboards need to have a way for fans and teams to collaborate.

This tutorial guides you through building an F1 dashboard with collaborative features such as real-time commenting, live user presence, and in-app notifications.

Tech stack and project setup

This tutorial uses the following tech stack:

  • Next.js: a full-stack React framework for building the app.
  • Velt: for adding the collaboration features.
  • Shadcn: UI component library for faster development.

Project setup

Use the following command to scaffold a Next.js project:

npx create-next-app@latest my-app --yes
Enter fullscreen mode Exit fullscreen mode

The --yes flag skips the prompts and creates a new Next.js project with saved preferences and defaults. The default setup enables TypeScript, Tailwind, ESLint, App Router, and Turbopack, with import alias @/*. This setup is enough for this tutorial.

Next, install Velt with the following command:

npm install @veltdev/react
Enter fullscreen mode Exit fullscreen mode

@veltdev/react is Velt’s SDK for React-based frameworks.

Initialize Shadcn using the following command:

npx shadcn@latest init
Enter fullscreen mode Exit fullscreen mode

You’ve set up your project. In the next sections, you’ll build the F1 dashboard.

But before that, what’s Velt, and why should you use Velt?

Why use Velt

If you’ve built a collaborative web app from the ground up, you know how much complexity goes into building the collaboration features.

You have to set up servers, manage WebSocket connections and synchronization, and handle the UI state changes.

Handling these tasks yourself will slow down your development process.

Velt solves this problem for you. Velt provides ready-made full-stack components that allow you to add real-time collaboration features to your app without writing any backend code. Velt handles the UI and backend synchronization, storage, and push notifications. You get to focus on the core features that make your app unique.

Part 1: Generating the Dashboard UI with v0

In this first part of the tutorial, you’ll use v0 to generate the UI of the dashboard.

What’s v0?

v0 is an AI tool with a chat-based interface that helps you build UIs by describing what you want. You type in your prompt, and v0 generates React and Tailwind CSS code using the latest frontend libraries like Next.js and Shadcn components.

Why are we using v0?

Instead of writing the entire layout by hand, we’ll use v0 to speed things up. This allows us to focus more on the core part of this tutorial: adding collaboration features to our dashboard.

The prompt to generate the UI of the dashboard

Use the following prompt to generate the UI of the dashboard:

Create a React app called "F1 Favorites" - A Formula 1 Historical Data Dashboard
Build a clean, modern collaborative dashboard UI for displaying Formula 1 historical race data. This should look like a professional team collaboration tool (similar to Notion, Linear, or Figma) with beautiful design and intuitive navigation.
API Integration Requirements:

Fetch and display race sessions data from: https://openf1.org/#sessions
The app should be ready to integrate driver data from: https://openf1.org/#drivers
The app should be ready to integrate car data from: https://openf1.org/#car-data
Focus ONLY on displaying accurate race sessions in the main table - keep other sections static for now

Design Requirements:

Create a sleek, collaborative dashboard aesthetic with modern UI patterns
Use a clean color scheme with good contrast and professional typography
Include a sidebar navigation with sections for: Dashboard, Race Sessions, Drivers, Cars, Analytics
Design it as if collaboration features will be added later (think team-oriented layout)
Make it responsive and visually appealing

Main Page Requirements:

Display a prominent table showing historical F1 race sessions from the API
Include relevant session details like date, location, session type, etc.
Add search/filter functionality for the sessions table
Keep the design clean - don't overcrowd with too much data

Overall Structure:

Header with app branding "F1 Favorites" and user profile area
Left sidebar navigation
Main content area with the sessions table as the focal point
Use placeholder content for Drivers, Cars, and Analytics sections
Add subtle loading states and empty states

Technical Notes:

Use React with modern hooks
Implement proper error handling for API calls
Use Tailwind CSS for styling
Make the code clean and well-organized
Add comments explaining where collaboration features will be integrated later

Design Inspiration:
Think modern SaaS dashboard with collaborative team features in mind. The UI should feel premium, clean, and ready for team collaboration features to be added seamlessly.
Focus on getting the race sessions API integration working perfectly with beautiful presentation, while keeping other sections as well-designed static placeholders.
Enter fullscreen mode Exit fullscreen mode

Copy the code generated and paste it into your project folder.

Your folder structure should be similar and extended version to this:

velt-f1-dashboard/
├─ components.json
├─ next.config.mjs

├─ app/
│  ├─ globals.css
│  ├─ layout.tsx
│  ├─ page.tsx
│  ├─ analytics/
│  │  └─ page.tsx
│  ├─ cars/
│  │  └─ page.tsx
│  ├─ drivers/
│  │  └─ page.tsx
│  ├─ favorites/
│  │  ├─ favorites-content.tsx
├─ components/
│  ├─ collaboration-setup.tsx
│  ├─ velt-wrapper.tsx
│  └─ ui/
│     ├─ avatar.tsx
│     ├─ badge.tsx
│     ├─ button.tsx
│     ├─ card.tsx
├─ contexts/
│  └─ user-context.tsx
├─ hooks/
├─ lib/
│  ├─ api.ts
│  └─ utils.ts
├─ public/
│  └─ (static assets)
├─ styles/
│  └─ globals.css
└─ utils/
   └─ country-flags.tsx

Enter fullscreen mode Exit fullscreen mode

Start your development environment, then open your web browser. You should see something like this:

F1 demo app

Now that you’re done with generating the UI of the app, you’re going to add the collaboration features in the next section.

Part 2: Adding real-time collaboration with Velt

In this second part of the tutorial, you’ll add the collaboration features to the app.

But before we proceed, I want to point out a few things:

First, you need an API key to work with Velt. Go to Velt’s console and get your free API key.

Here’s something you should know: you need to upgrade to the paid version to use Velt in production.

Second, this tutorial focuses on the part of the app where you’ll add the collaboration features. The important parts are dashboard-layout.tsx, drivers-table.tsx, and race-sessions-table.tsx.

Lastly, the code snippets will be focused on the relevant parts. That means that the UI and styling will be omitted for the most part. The link to the full working code is in the Resources section.

Setting up the user authentication system

This system allows Velt to identify your app users.

The user authentication system for this tutorial uses the React Context API and a mock users array. In your app, you’d fetch users using your app’s auth function/system.

Open your user-context.tsx file, and let’s understand what the code does in bits (v0 will most likely generate this file. If not, refer to the repo for the full working code).

First, you define the racing team members who'll collaborate on the dashboard. Each user has team-specific colors and avatars that match their racing persona:

const USERS = [
  {
    id: "alex-thunder-v2",
    userId: "alex-thunder-v2",
    organizationId: "f1-racing-hub-2024",
    name: "Alex Thunder",
    email: "alex@speedracing.com",
    avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=AlexThunder",
    initials: "AT",
    color: "#FF1744", // Ferrari red
  },
  {
    id: "jordan-swift-v2",
    userId: "jordan-swift-v2",
    organizationId: "f1-racing-hub-2024",
    name: "Jordan Swift",
    email: "jordan@velocityteam.com",
    avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=JordanSwift",
    initials: "JS",
    color: "#00BCD4", // Mercedes cyan
  },
];

const DOCUMENT_ID = "f1-dashboard-v2-2024";
const USER_SELECTION_KEY = "f1-selected-user";
Enter fullscreen mode Exit fullscreen mode

What this code snippet does: Creates two F1 team members with distinct racing identities. Alex Thunder uses Ferrari red (#FF1744), while Jordan Swift uses Mercedes cyan (#00BCD4). The DOCUMENT_ID serves as the shared collaboration space, and USER_SELECTION_KEY persists user choices across sessions.

Managing User State with React Context

Next, you create a React Context provider to manage user authentication state across the entire application:

const UserContext = createContext<UserContextType | undefined>(undefined);

export function UserProvider({ children }: { children: React.ReactNode }) {
  const [currentUser, setCurrentUserState] = useState(USERS[0]);
  const [isInitialized, setIsInitialized] = useState(false);
  const { client } = useVeltClient();
Enter fullscreen mode Exit fullscreen mode

What this code snippet does: Sets up a shared state management system using React Context. The UserProvider wraps your entire F1 dashboard, making the current user and collaboration status available to all components. The isInitialized state tracks when Velt is ready for real-time collaboration.

Restoring User Sessions from localStorage

When users return to the dashboard, we restore their previous selection:

useEffect(() => {
  const savedUserId = localStorage.getItem(USER_SELECTION_KEY);
  if (savedUserId) {
    const savedUser = USERS.find((u) => u.userId === savedUserId);
    if (savedUser) {
      setCurrentUserState(savedUser);
    }
  }
}, []);
Enter fullscreen mode Exit fullscreen mode

What this code snippet does: Checks localStorage on component mount for a previously selected user. If found (like when Alex Thunder returns to the dashboard), it automatically sets them as the current user, creating a seamless multi-session experience.

Initializing Velt for Real-Time Collaboration

This is where you connect users to Velt:

useEffect(() => {
  if (client && currentUser && !isInitialized) {
    const initializeVelt = async () => {
      try {
        await client.identify({
          userId: currentUser.userId,
          organizationId: currentUser.organizationId,
          name: currentUser.name,
          email: currentUser.email,
          photoUrl: currentUser.avatar,
        });

        await client.setDocument(DOCUMENT_ID, {
          documentName: "F1 Favorites Dashboard",
        });

        setIsInitialized(true);
        console.log("Velt initialized with user:", currentUser.name);
      } catch (error) {
        console.error("Failed to initialize Velt:", error);
      }
    };

    initializeVelt();
  }
}, [client, currentUser, isInitialized]);
Enter fullscreen mode Exit fullscreen mode

What this code snippet does: When both the Velt client and a user are ready, this function:

  1. Identifies the user to Velt with client.identify() - telling Velt, "Alex Thunder is now online."
  2. Sets the document context with client.setDocument() - creating a shared space called "F1 Favorites Dashboard."
  3. Marks the initialization as complete, so the dashboard knows collaboration features are ready.

Switching Between Racing Team Members

Users can switch personas to simulate different team members collaborating:

const setCurrentUser = async (user: (typeof USERS)[0]) => {
  if (!client) return;

  try {
    localStorage.setItem(USER_SELECTION_KEY, user.userId);
    await client.signOutUser();
    setCurrentUserState(user);
    setIsInitialized(false);
    console.log("User switched to:", user.name);
  } catch (error) {
    console.error("Failed to switch user:", error);
  }
};
Enter fullscreen mode Exit fullscreen mode

What this code snippet does: Handles the complete user switching workflow:

  1. Saves selection to localStorage for persistence.
  2. Properly signs out the current user from Velt with client.signOutUser().
  3. Updates state and triggers re-initialization for the new user.

This ensures a smooth transition when switching between "Alex Thunder" and "Jordan Swift" during collaboration sessions.

You won’t have to worry about this in your app, as users will only have to sign in and out of their accounts.

Making User Context Accessible

Finally, we expose the user management system through a custom hook:

export function useUser() {
  const context = useContext(UserContext);
  if (context === undefined) {
    throw new Error("useUser must be used within a UserProvider");
  }
  return context;
}
Enter fullscreen mode Exit fullscreen mode

What this code snippet does: Creates a clean, type-safe hook (useUser()) that any component can use to access the current user, switch users, or check initialization status, following React best practices for context consumption.

This user authentication system creates a seamless collaboration experience for F1 racing teams. The context provider manages everything from user persistence and Velt initialization to team member switching. By handling user management and Velt integration in one place, you can focus on building the actual dashboard features.

Configuring Velt for your app

Velt provides a Provider component that enables Velt in your app.

You’ll create 2 files/components fr wrapping your app in the VeltProvider component.

Create a velt-provider.tsx file and add the following code:

"use client"

import { VeltProvider } from '@veltdev/react';

export function VeltProviderWrapper({ children }: React.ReactNode) {
  return (
    <VeltProvider apiKey={process.env.NEXT_PUBLIC_VELT_API_KEY!}>
      {children}
    </VeltProvider>
  )
}
Enter fullscreen mode Exit fullscreen mode

The VeltProviderWrapper component is a wrapper over the VeltProvider component by Velt. You pass in your API key.

Next, create a velt-wrapper.tsx file and add the following code:

"use client"

import dynamic from 'next/dynamic';

const VeltProviderWrapper = dynamic(
  () => import('./velt-provider').then((mod) => ({ default: mod.VeltProviderWrapper })),
  {
    ssr: false,
    loading: () => null
  }
)

export function VeltWrapper({ children }: React.ReactNode) {
  return <VeltProviderWrapper>{children}</VeltProviderWrapper>
}
Enter fullscreen mode Exit fullscreen mode

Let's break down what's happening in this code:

  1. You defined a VeltWrapper component that dynamically imports the VeltProviderWrapper. This approach prevents the Velt SDK from being included in server-side rendering, which is important for client-only collaboration features.
  2. The ssr: false option ensures Velt only loads in the browser, optimizing performance and preventing hydration mismatches since real-time collaboration features require client-side JavaScript.

Navigate to your layout.tsx and update it with the following code:

import { VeltWrapper } from "@/components/velt-wrapper";
import { UserProviderWrapper } from "@/components/user-provider-wrapper";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <VeltWrapper>
          <UserProviderWrapper>{children}</UserProviderWrapper>
        </VeltWrapper>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

Adding Comments

In this subsection, you’ll add comments to the dashboard.

You’ll add the commenting features in these components: DashboardLayout, RaceSessionsTable, and DriversTable components.

Open the DashboardLayout file and add the VeltCommentSidebar and VeltSidebarButton components:

import { VeltCommentsSidebar, VeltSidebarButton } from "@veltdev/react";

function DashboardLayout() {
  return (
    <div>
      {/* Other layout content */}
      <VeltCommentsSidebar darkMode={true} />
      <VeltSidebarButton darkMode={true} />
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

You added the two key Velt sidebar components:

  • VeltCommentsSidebar: holds all existing comments.
  • VeltSidebarButton: toggle button to show/hide the sidebar.

This creates a unified interface for discussion with minimal code.

Update the RaceSessionsTable component:

import { VeltCommentTool, VeltCommentBubble } from "@veltdev/react";

function RaceSessionsTable() {
  return (
    <div>
      {/* Other table content */}
      <VeltCommentTool
        targetElementId={`session-row-${session.session_key}`}
        darkMode={isDarkMode}
      >
        <Button>
          <MessageSquare className="w-4 h-4" />
        </Button>
      </VeltCommentTool>
      <VeltCommentBubble
        targetElementId={`session-row-${session.session_key}`}
        darkMode={isDarkMode}
      />
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

You added the two key Velt inline commenting components:

  • VeltCommentTool: Creates comment buttons attached to specific race sessions with unique targetElementId.
  • VeltCommentBubble: shows comment count and the author’s avatar.

This creates a consistent commenting system for discussions with minimal code.

Update the DriversTable component:

import { VeltCommentTool, VeltCommentBubble } from "@veltdev/react";

function DriversTable() {
  return (
    <div>
      {/* Other table content */}
      <VeltCommentTool
        targetElementId={`driver-row-${driver.driver_number}-${driver.session_key}`}
        darkMode={isDarkMode}
      >
        <Button>
          <MessageSquare className="w-4 h-4" />
        </Button>
      </VeltCommentTool>
      <VeltCommentBubble
        targetElementId={`driver-row-${driver.driver_number}-${driver.session_key}`}
        darkMode={isDarkMode}
      />
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

You added the same Velt inline commenting components to the drivers table: VeltCommentTool and VeltCommentBubble.

Note: by adding VeltComments, you enable a range of collaboration features by default. These features include threaded replies, @mentions, read receipts, and being able to resolve comment threads.

Now that you’re done with adding comments to your F1 dashboard, the next step is to add live user presence.

Adding Live Presence

In this section, you’ll add live presence to your app. Presence allows users to see other users who are online in a particular document.

You’ll add the Velt component for showing users who are currently online in the DashboardLayout component.

Update your DashboardLayout component:

import { VeltPresence } from "@veltdev/react";

function DashboardLayout() {
  return (
    <div>
      {/* Other layout content */}
      <VeltPresence flockMode={true} maxUsers={2} />
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

You added the VeltPresence component to display active team members:

  • flockMode={true}: Shows user avatars in a compact, grouped display.
  • maxUsers={2}: Limits visible users to two. You can increase the limit.

This creates real-time visibility into who's collaborating on race strategies without cluttering the dashboard interface.

Adding In-App Notification

In-app Notifications allow users to receive email notifications when they’re mentioned in a document.

To add notifications to your app, you’ll have to enable notifications in your Velt console.

Go to the Notifications section in the Configurations section of the Velt Console and enable Notifications.

Update your DashboardLayout component to add the VeltNotificationTool, which is the component for toggling the notification panel.

import { VeltNotificationsTool } from "@veltdev/react";

export function DashboardLayout() {
  return (
    <div className="min-h-screen">
        {/* Other layout content */}
      {/* Velt Notifications Tool - Notification bell for collaboration alerts */}
      <VeltNotificationsTool darkMode={isDarkMode} />
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

You added the VeltNotificationsTool component with automatic theme detection (darkMode={isDarkMode}) to your dashboard layout. The component opens the Notifications Panel that shows all Notifications grouped in categories.

Demo

Start your development server using the following command:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Open your browser, and you should see something like this:

Conclusion

You’ve built an F1 dashboard with real-time collaboration features: Comments, Presence, and in-app Notifications.

The best part? You didn’t handle any of the backend. Velt handles the backend logic, which allows you to focus on the core features of your app.

If you're building your own leaderboard, you can extend your app further by adding other powerful Velt features like screen recording, cursor tracking, reactions, and real-time huddles. Adding these features can make your app more robust and give your users an even better collaborative experience.

Resources

Top comments (0)