惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
博客园 - 三生石上(FineUI控件)
MyScale Blog
MyScale Blog
Apple Machine Learning Research
Apple Machine Learning Research
L
LINUX DO - 最新话题
T
The Blog of Author Tim Ferriss
P
Proofpoint News Feed
宝玉的分享
宝玉的分享
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
Visual Studio Blog
爱范儿
爱范儿
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
月光博客
月光博客
博客园 - 叶小钗
D
Docker
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Tailwind CSS Blog
D
DataBreaches.Net
酷 壳 – CoolShell
酷 壳 – CoolShell
B
Blog RSS Feed
量子位
美团技术团队
Vercel News
Vercel News
Y
Y Combinator Blog
IT之家
IT之家
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
SegmentFault 最新的问题
腾讯CDC
Recent Announcements
Recent Announcements
Google DeepMind News
Google DeepMind News
罗磊的独立博客
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
The Register - Security
The Register - Security
博客园 - 司徒正美
N
Netflix TechBlog - Medium
S
Schneier on Security
博客园 - 聂微东
U
Unit 42
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
雷峰网
雷峰网
Latest news
Latest news

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Building a Zulip Style Collaborative Chat App with Next.js and Velt
Astrodevil · 2026-05-06 · via DEV Community

Zulip is known for keeping conversations organized. Topic-based threads, clear context, and async-friendly discussions make it a favorite for technical and distributed teams. Unlike traditional chat apps, conversations in Zulip stay readable even as teams scale.

Recreating this experience can be a bit difficult. Real-time messaging, user presence, inline comments, and notifications usually require a complex backend and real-time infrastructure.

In this tutorial, we will build a Zulip-style collaborative chat application using Next.js, Tailwind CSS, and Velt. Next.js powers the UI and application structure, while Velt adds collaboration features like presence, comments, and notifications without writing backend code.

By the end, you will have a working chat interface inspired by Zulip, with real-time collaboration built in and ready to extend.

Why Zulip’s Approach Works

Zulip’s design solves a problem most chat tools struggle with: conversations getting messy over time.

Instead of long, linear message streams, Zulip organizes discussions into topics. Each message belongs to a clear context, so conversations stay focused and easy to follow even days or weeks later.

Three ideas make this work especially well:

  • Real-time collaboration: Messages, comments, and updates appear instantly for everyone in the channel.
  • Context-rich discussions: Replies stay tied to a specific topic or message, so feedback does not get lost in the noise.
  • Visible presence: You always know who is online and actively participating, which makes collaboration feel immediate and shared.

What makes this challenging to build in a SaaS product is not the UI, but the infrastructure behind it. Real-time messaging, presence tracking, comments, and notifications typically require WebSockets, backend event systems, data synchronization, and careful handling of concurrent users. Building and maintaining this reliably can take significant engineering effort.

In this tutorial, we recreate Zulip’s collaborative behavior without building that infrastructure ourselves, by using Velt as the collaboration layer.

Zulip Chat

What We’re Building With

Before we dive into the code, let’s quickly look at the tools we’ll use and why they fit this project well.

  • Next.js: Next.js gives us a solid foundation for building interactive applications. With the App Router, we get a clean layout structure and client-side interactivity that works well for chat-style interfaces.
  • React: React handles the UI composition and state updates. The chat interface, message list, and user interactions all rely on simple, predictable React patterns.
  • Tailwind CSS: Tailwind helps us build a clean and modern UI quickly. It keeps styling close to the components and makes it easy to adjust layouts, spacing, and themes without writing custom CSS.
  • shadcn ui (powered by Radix UI): These provide accessible, reusable UI primitives like buttons, dropdowns, and avatars. They give us a polished look without locking us into heavy component libraries.
  • Zustand: Zustand is used for lightweight state management. In this project, it manages demo users and allows us to switch between them to test real-time collaboration.
  • Velt: Velt is the key piece. Instead of building real-time infrastructure ourselves, Velt provides presence, comments, notifications, and collaboration out of the box. Once integrated, features like reactions, read status, and threaded comments work automatically without writing extra backend or real-time code.

Together, this stack lets us focus on the chat experience and UI, while Velt handles the collaboration layer behind the scenes.

Prerequisites

To follow along with this tutorial, you’ll need:

  • Node.js (v16 or later) installed on your machine
  • Basic familiarity with Next.js, React, and TypeScript
  • A Velt account (you can sign up for free at velt.dev)
  • Working knowledge of Tailwind CSS fundamentals

You do not need:

  • Prior experience with Velt
  • Any backend or database setup
  • Experience building real-time systems

We’ll walk through the collaboration setup step by step, and the app runs entirely on the frontend.

Project Setup

Instead of scaffolding a new project from scratch, we’ll start from an existing Zulip style chat app that already has the UI and collaboration logic wired up. This lets us focus on understanding how the pieces fit together.

Clone the Repository

Begin by cloning the project and moving into the directory:

gitclone https://github.com/Studio1HQ/zulip-velt
cd zulip-velt

Enter fullscreen mode Exit fullscreen mode

Install Dependencies

Install all required dependencies using npm:

npm install

Enter fullscreen mode Exit fullscreen mode

This will install Next.js, Tailwind CSS, shadcn ui components, Zustand for state management, and the Velt SDK used for collaboration features.

Configure the Velt API Key

Velt requires a public API key to enable collaboration features like presence, comments, and notifications.

Create a .env.local file in the root of the project and add:

NEXT_PUBLIC_VELT_ID=your_velt_api_key_here

Enter fullscreen mode Exit fullscreen mode

You can generate this key from the Velt dashboard after creating a free account.

Once the key is added, restart the development server if it is already running.

Velt dashboard

Run the App Locally

Start the development server:

npm run dev

Enter fullscreen mode Exit fullscreen mode

Open your browser and visit:

http://localhost:3000

Enter fullscreen mode Exit fullscreen mode

You should now see a Zulip-style chat interface with channels on the left, a message area in the center, and collaboration controls in the header.

At this point, the UI is already functional. In the next sections, we’ll break down how the app is structured and how Velt is integrated to power real-time collaboration without any backend setup.

Zulip chat2

Project Setup

At a high level, the project is divided into five main parts:

  • app: Handles routing, layouts, and global configuration using the Next.js App Router
  • components: Contains all reusable UI and chat-related components
  • helper: Manages demo user data and user switching logic
  • hooks: Stores custom React hooks such as theme management
  • lib: Holds small utility functions used across the app

Let’s take a closer look at the most important folders we’ll work with in this tutorial.

Understanding the App Router and Layout Setup

This project uses the Next.js App Router, which organizes the application using layouts and pages instead of traditional route files. If you are new to the App Router, don’t worry. We’ll focus only on what matters for this app.

The key idea is simple: layouts wrap pages, and this is where we place Velt and theme handling.

In this project, there are two layout files, each with a different responsibility.

layout.tsx is the root layout for the entire application. It defines the HTML structure, metadata, and global styles.

"use client";

import { ThemeProvider } from "@/hooks/use-theme";
import { VeltProvider } from "@veltdev/react";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <VeltProvider apiKey={process.env.NEXT_PUBLIC_VELT_ID || ""}>
      <ThemeProvider>{children}</ThemeProvider>
    </VeltProvider>
  );
}

Enter fullscreen mode Exit fullscreen mode

page.tsx - This file renders the main chat interface. You’ll see it importing layout and chat components and placing them on the page.

"use client"

import { useState, useEffect } from 'react'
import { Header } from '@/components/layout/header'
import { Sidebar } from '@/components/layout/sidebar'
import { ChatArea } from '@/components/chat/chat-area'

export default function Home() {
  const [isSidebarOpen, setIsSidebarOpen] = useState(true)
  const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false)
  const [isMobile, setIsMobile] = useState(false)

  useEffect(() => {
    const checkMobile = () => {
      const mobile = window.innerWidth < 768
      setIsMobile(mobile)
      if (mobile) {
        setIsSidebarOpen(false)
        setIsSidebarCollapsed(false)
      }
    }

    checkMobile()
    window.addEventListener('resize', checkMobile)
    return () => window.removeEventListener('resize', checkMobile)
  }, [])

  const toggleSidebar = () => {
    if (isMobile) {
      setIsSidebarOpen(!isSidebarOpen)
    } else {
      if (isSidebarCollapsed) {
        setIsSidebarCollapsed(false)
      } else if (isSidebarOpen) {
        setIsSidebarCollapsed(true)
      } else {
        setIsSidebarOpen(true)
      }
    }
  }

  const closeSidebar = () => {
    if (isMobile) {
      setIsSidebarOpen(false)
    }
  }

  return (
    <div className="h-screen flex flex-col bg-background">
      <Header 
        onToggleSidebar={toggleSidebar}
        isSidebarOpen={isSidebarOpen}
        isMobile={isMobile}
      />
      <div className="flex-1 flex overflow-hidden">
        <Sidebar 
          isOpen={isSidebarOpen}
          isCollapsed={isSidebarCollapsed}
          isMobile={isMobile}
          onClose={closeSidebar}
        />
        <ChatArea />
      </div>
    </div>
  )
}

Enter fullscreen mode Exit fullscreen mode

  • app/layout.tsx is the global shell
  • app/(app)/layout.tsx is where Velt is initialized
  • page.tsx is where the chat UI begins
  • Providers are placed at layout level so all components can access them

Building the Chat Interface with Components

Most of the Zulip-style experience in this app is built using reusable React components. Each part of the interface has a clear responsibility, which makes the code easier to read and extend.

We’ll focus on three areas:

  • The chat area where messages appear
  • The message component itself
  • The layout components that shape the overall UI

Chat Area:

chat-area.tsx - This component is responsible for rendering the list of messages and attaching collaboration features to them.

message.tsx - Each message in the chat is rendered as its own component. This keeps the UI modular and makes it easy to attach collaboration features at the message level.

Layout Components:

The layout of the app is built using two main components: a header and a sidebar.

  • The sidebar represents Zulip style channels and navigation.
  • The header is where collaboration controls live.

Inside the header, you’ll see Velt components like:

<VeltPresence />
<VeltNotificationsTool />
<VeltCommentsSidebar />
<VeltSidebarButton />

Enter fullscreen mode Exit fullscreen mode

What these do:

  • VeltPresence shows who is currently online
  • VeltNotificationsTool displays collaboration notifications
  • VeltCommentsSidebar Opens a panel with all comments
  • VeltSidebarButton toggles the comments sidebar

All of these work automatically once Velt is initialized at the layout level.

import {
  VeltPresence,
  VeltNotificationsTool,
  VeltSidebarButton,
  VeltCommentsSidebar,
  useVeltClient,
} from "@veltdev/react";
import { names, userIds, useUserStore } from "@/helper/userdb";

interface HeaderProps {
  onToggleSidebar: () => void;
  isSidebarOpen: boolean;
  isMobile: boolean;
}

export function Header({
  onToggleSidebar,
  isSidebarOpen,
  isMobile,
}: HeaderProps) {
  const { theme } = useTheme();
  const { user, setUser } = useUserStore();
  const { client } = useVeltClient();
  const prevUserRef = useRef(user);
  const isInitializingRef = useRef(false); // Prevent overlapping initialization calls

Enter fullscreen mode Exit fullscreen mode

Zulip dashboard

Client Initialization, User Identification, and Document Setting

 // Handle Velt client initialization, user identification, and document setting
  useEffect(() => {
    if (!client || !user || isInitializingRef.current) {
      console.log("Velt init skipped:", {
        client: !!client,
        user: !!user,
        initializing: isInitializingRef.current,
      });
      return;
    }

    const initializeVelt = async () => {
      isInitializingRef.current = true;
      try {
        // Detect user switch
        const isUserSwitch = prevUserRef.current?.uid !== user.uid;
        prevUserRef.current = user;

        console.log("Starting Velt init for user:", user.uid, { isUserSwitch });

        // Re-identify the user (handles initial and switches)
        const veltUser = {
          userId: user.uid,
          organizationId: "organization_id",
          name: user.displayName,
          email: user.email,
          photoUrl: user.photoUrl,
        };
        await client.identify(veltUser);
        console.log("Velt user identified:", veltUser.userId);
        await client.setDocuments([
          {
            id: "zulip-velt",
            metadata: { documentName: "zulip-velt" },
          },
        ]);
        console.log("Velt documents set: zulip-velt");
      } catch (error) {
        console.error("Error initializing Velt:", error);
      } finally {
        isInitializingRef.current = false;
      }
    };

    initializeVelt();
  }, [client, user]); // Re-run on client or user change

Enter fullscreen mode Exit fullscreen mode

Reusable UI Components

To keep the chat interface clean and consistent, this project uses a set of reusable UI components. These components handle common UI patterns like buttons, inputs, avatars, and scroll areas.

Instead of building these from scratch, the project uses shadcn ui, which is a collection of accessible, unstyled components built on top of Radix UI. This approach gives us flexibility while keeping the UI consistent.

All reusable UI components live inside the components/ui folder.

  • Reusable UI primitives: Shared building blocks like buttons, inputs, avatars, and dropdowns used across the app to keep styling consistent.
  • Built with shadcn ui (powered by Radix UI): Accessible, unstyled components that provide behavior and keyboard support without locking the design.
  • Scroll handling for chat: The ScrollArea component manages smooth scrolling for long message lists in the chat view.
  • Separation of concerns: UI components handle appearance and interaction, while chat and layout components focus on structure and logic.
  • Easy to extend and customize: Updating a UI component in this folder automatically updates its usage across the entire app.

User Management for Collaboration Testing

To test real-time collaboration features like presence, comments, and notifications, we need a way to simulate multiple users. Instead of setting up a full authentication system, this project uses a simple and beginner-friendly approach with predefined users.

User state is managed using Zustand, a lightweight state management library.

import { create } from "zustand";
import { persist } from "zustand/middleware";

export type User = {
  uid: string;
  displayName: string;
  email: string;
  photoUrl?: string;
};

export interface UserStore {
  user: User | null;
  setUser: (user: User) => void;
}

export const userIds = ["user001", "user002"];
export const names = ["Nany", "Mary"];

export const useUserStore = create<UserStore>()(
  persist(
    (set) => ({
      user: null,
      setUser: (user) => set({ user }),
    }),
    {
      name: "user-storage",
    }
  )
);

Enter fullscreen mode Exit fullscreen mode

Theme Management with useTheme

Modern apps usually support both light and dark themes, and this project follows the same pattern. Theme state is managed using a custom React hook, which keeps the logic reusable and easy to maintain.

The theme logic lives inside a single file.

The theme is:

  • Stored in React state
  • Loaded from localStorage on page load
  • Saved back to localStorage when changed

This ensures the user’s theme preference persists across page refreshes.

Syncing Theme with Velt

The theme value is also passed to Velt components, allowing them to automatically switch between light and dark modes.

This means:

  • The app UI and Velt UI stay visually consistent
  • No extra styling logic is required for collaboration components
  • Once the hook is set up, theme changes propagate everywhere.

Utility Functions

The lib folder contains small helper utilities that are shared across the application. In this project, it mainly exists to support reusable UI components and keep common logic out of individual files. The utilities in this file are primarily related to class name handling. When working with Tailwind CSS and reusable components, it’s common to conditionally apply multiple class names.

Running the App and Testing the Output

Now that we’ve walked through the project structure and key files, it’s time to run the app and verify that everything works as expected.

Seeing the app in action makes the collaboration features easier to understand in the next sections.

From the project root, run:

npm run dev

Enter fullscreen mode Exit fullscreen mode

Once the server starts, open your browser and navigate to:

http://localhost:3000

Enter fullscreen mode Exit fullscreen mode

You should see a Zulip style chat interface with:

  • A sidebar for channels
  • A header with user controls
  • A central chat area for messages

To test collaboration features, open the app in two browser windows or one normal window and one incognito window.

Use the user switcher in the header to change between demo users such as Nany and Mary.

Each window should represent a different user.

Chat Window

Try the following actions:

  • Presence: Notice user avatars appear in the header when multiple users are online.
  • Comments: Hover over a message and add a comment. The comment should appear instantly in both windows.
  • Notifications: Add a comment in one window and check the notification bell in the other.
  • Theme Switching: Toggle between light and dark mode and observe that both the app and Velt components update correctly.

How It Works

Here’s a simple view of what happens when the app runs:

  • The app loads and initializes Velt using the API key in the layout
  • A demo user is selected using the Zustand store
  • Velt identifies the active user and associates them with the app
  • A shared document context is set for collaboration
  • Messages render in the chat area
  • Comments, presence, and notifications are automatically tracked by Velt
  • Updates sync instantly across all connected clients

The key idea is that the app focuses on UI and user flow, while Velt handles real-time synchronization and collaboration logic.

Collaborative Features You Get Automatically

Once Velt is integrated, collaboration features start working without writing additional backend or real-time code.

These features are available out of the box:

  • Inline comments: Add comments directly on chat messages and view them in context.
  • User presence: See who is currently online and active in the chat.
  • Notifications: Get notified when someone comments or interacts.
  • Reactions and comment status: React to comments and mark them as resolved or active.
  • Read awareness: See when comments have been viewed by other users.

All of these features work automatically once Velt is set up.

Things to Note Before Shipping to Production

Before using this setup in a real product, keep the following in mind:

  • Replace demo users with a real authentication system
  • Identify users using real user IDs and metadata
  • Use dynamic document IDs instead of a hardcoded value
  • Add permissions to control who can view or comment
  • Handle error states when collaboration services are unavailable
  • Review Velt customization options to match your product UI
  • Test performance with multiple concurrent users

These steps ensure the app scales safely beyond a demo environment.

Demo Video

You can see the completed Zulip style chat app with real-time collaboration in action here:

Live Demo:

https://zulip-velt-chat.vercel.app/

This demo shows user switching, comments, presence, notifications, and theme support working together.

Conclusion

Zulip demonstrates how powerful chat can be when collaboration is built into the experience. Organized conversations, clear context, and real-time awareness turn chat from simple messaging into a system teams can rely on.

For builders, the real challenge is not the UI. It is the collaboration layer behind it. Features like real-time sync, presence tracking, inline comments, notifications, and reactions usually require complex infrastructure and long development cycles. This is especially true when building a chat-based SaaS product.

In this tutorial, we focused on that exact problem. Instead of rebuilding collaboration from scratch, we showed how to add Zulip-style collaborative features inside a chat application using Next.js and a modern frontend stack, while delegating the hard real-time problems to Velt.

The result is a working chat app where collaboration feels native, but the code stays simple and frontend-focused.

Key Takeaways

  • Clean component structure makes complex apps easier to understand
  • Collaboration does not need custom real-time infrastructure
  • Velt enables comments, presence, notifications, and more out of the box
  • Frontend-focused development speeds up experimentation and iteration

If you are building a chat app, internal tool, or collaborative product, Velt lets you focus on your core experience instead of infrastructure.

Try building with Velt and add collaboration to your app in minutes at velt.dev.