🪟 Windows TippsThe Gemini desktop app is now available for Windows(11.09.2026 um 17:06 Uhr)
⚠️ Malware / Trojaner / VirenWindows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC(10.09.2026 um 20:11 Uhr)
⚠️ Malware / Trojaner / VirenVorsicht: Android-Malware verschlüsselt Ihre Handys und nimmt heimlich Fotos auf(11.09.2026 um 09:35 Uhr)
🕵️ SicherheitslückenMicrosoft geht endlich eines der nervigsten Probleme von Windows 11 an(11.09.2026 um 11:58 Uhr)
💾 IT Security ToolsSysinternals Suite(11.09.2026 um 12:00 Uhr)
🕵️ SicherheitslückenDefender 0-Day ShieldBreak (CVE-2026-69414) nicht sauber gepatcht - BornCity(11.09.2026 um 12:52 Uhr)
🪟 Windows TippsThe Gemini desktop app is now available for Windows(11.09.2026 um 17:06 Uhr)
⚠️ Malware / Trojaner / VirenWindows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC(10.09.2026 um 20:11 Uhr)
⚠️ Malware / Trojaner / VirenVorsicht: Android-Malware verschlüsselt Ihre Handys und nimmt heimlich Fotos auf(11.09.2026 um 09:35 Uhr)
🕵️ SicherheitslückenMicrosoft geht endlich eines der nervigsten Probleme von Windows 11 an(11.09.2026 um 11:58 Uhr)
💾 IT Security ToolsSysinternals Suite(11.09.2026 um 12:00 Uhr)
🕵️ SicherheitslückenDefender 0-Day ShieldBreak (CVE-2026-69414) nicht sauber gepatcht - BornCity(11.09.2026 um 12:52 Uhr)

🔧 Programmierung 🕛 vor 1 Jahr 58 Min Lesezeit
0

Building a Slack Clone with Next.js and TailwindCSS - Part One

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Collaboration is essential for success, and creating a tool that helps teams work better can be fun and rewarding. With more people working from home, building an app that helps everyone stay connected through messaging, video, and group chats can make a big difference.



In this three-part series, we will build a .



In this first part, we'll set up the basics by setting up the project and building the first user interface, including the channel page.



In part two, we'll add real-time messaging and channels using and add the final touches.



By the end of this series, you'll have built a robust collaboration app that mirrors the essential features of Slack.



Here's a glimpse of what the final product will look like:







You can check out the .



Let's get started!






Prerequisites



Before starting the project, make sure you have the following:




  • Basic Understanding of React: You should be comfortable building components, managing state, and understanding how components work.


  • Node.js and npm: Ensure Node.js and npm (Node Package Manager) are installed on your computer. This is important for running and building our project.


  • Familiarity with TypeScript, Next.js, and TailwindCSS Basics: We'll use these tools a lot, so knowing the basics will help you follow along easily.







Project Setup



Let's start by setting up our project. We'll begin by cloning a starter template that contains the initial setup code and folder structure to help us get started quickly:




CODE
# Clone the repository
git clone https://github.com/TropicolX/slack-clone.git

# Navigate into the project directory
cd slack-clone

# Check out the starter branch
git checkout starter

# Install the dependencies
npm install






The project structure should look like the following:



is an open-source ORM (Object-Relational Mapping) tool that lets us define our database structure and run queries efficiently. With Prisma, you can write database operations more intuitively without needing to handle SQL directly, which makes things simpler and reduces errors.






Installing Prisma



Let’s start by installing Prisma and its dependencies:




CODE
npm install prisma --save-dev
npm install @prisma/client sqlite3






The @prisma/client library helps us interact with the database and sqlite3 is the database we will use for this project.



After installing, let’s initialize Prisma with the following command:




CODE
npx prisma init






This command sets up the default Prisma structure and creates a new .env file where we will configure our database connection.






Setting Up the Database Schema



Now, let's define our database schema. Open the prisma/schema.prisma file and add the following:




CODE
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}

generator client {
provider = "prisma-client-js"
}

model Workspace {
id String @id @default(cuid())
name String
image String?
ownerId String
channels Channel[]
memberships Membership[]
invitations Invitation[]
}

model Channel {
id String @id @default(cuid())
name String
description String?
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id])
}

model Membership {
id String @id @default(cuid())
userId String
email String
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id])
role String? @default("member")
joinedAt DateTime? @default(now())
@@unique([userId, workspaceId])
}

model Invitation {
id Int @id @default(autoincrement())
email String
token String @unique
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id])
invitedById String
acceptedById String?
createdAt DateTime @default(now())
acceptedAt DateTime?
}






This schema defines the primary relationships in our Slack clone. Here's what each model does:




  • Workspace: Represents a workspace where people can collaborate. It contains information like the workspace name, image, and the list of channels, memberships, and invitations linked to it.


  • Channel: Represents a channel within a workspace. Channels are where users can have specific discussions, and they belong to a particular workspace.


  • Membership: Keeps track of which users are part of which workspace. It includes details like the user ID, email, role (e.g., member), and when they joined the workspace.


  • Invitation: Manages invitations to join a workspace. It tracks the invitee's email, a unique token for the invitation, who invited them, and whether or not the invitation has been accepted.




Each model has its own details and connections, making it easy to get related data as we build features.



Next, let’s set up our database connection. Navigate to your .env file and add the following:




CODE
DATABASE_URL=file:./dev.db






This sets up SQLite as our database for local development. You could switch to another database in production, but SQLite is great for quick prototyping and development.






Running Prisma Migrations



To create the database tables based on our schema, run the following command:




CODE
npx prisma migrate dev --name init






This command sets up the tables for the models we defined in the database. It also helps us keep track of changes in our database setup during development.



After running the migration, generate the Prisma client by running the following command:




CODE
npx prisma generate






This command creates the Prisma client, which lets us work with the database safely and reliably throughout our code.






Setting Up Prisma Client in Code



To use the Prisma client in our project, create a new prisma.ts file in the lib directory with the following code:




CODE
import { PrismaClient } from '@prisma/client';

let prisma: PrismaClient;

if (process.env.NODE_ENV === 'production') {
prisma = new PrismaClient();
} else {
// @ts-expect-error global.prisma is used by @prisma/client
if (!global.prisma) {
// @ts-expect-error global.prisma is used by @prisma/client
global.prisma = new PrismaClient();
}
// @ts-expect-error global.prisma is used by @prisma/client
prisma = global.prisma;
}
export default prisma;






This script makes sure we only create one Prisma client instance. We use a global instance during development to avoid problems with too many database connections. This is especially useful because frequent restarts or hot reloading can otherwise lead to connection issues.






User Authentication with Clerk






What is Clerk?





First, you'll need to create a free account with Clerk. Go to the



After signing in, you can create a new project in Clerk for your app:




  1. Go to the dashboard and click "Create application".


  2. Name your application “Slack clone”.


  3. Under “Sign in options,” choose Email, Username, and Google.


  4. Click the "Create application" to complete the setup.






Next, we’ll make the first and last names required fields during sign-up:




  1. Navigate to your dashboard's "Configure" tab.


  2. Under "User & Authentication", select "Email, Phone, Username".


  3. Find the "Name" option in the "Personal Information" section and toggle it on..


  4. Click the gear icon next to "Name" and set it as required.


  5. Click “Continue” to save your changes.







Installing Clerk in Your Project



Next, let's add Clerk to your Next.js project:





  1. Install the Clerk package by running the command below:


    CODE
    npm install @clerk/nextjs




  2. Create an .env.local file and add the following environment variables:


    CODE
    NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key
    CLERK_SECRET_KEY=your_clerk_secret_key



    Replace your_clerk_publishable_key and your_clerk_secret_key with the keys from your project's overview page.




  3. To use Clerk's authentication throughout your app, you need to wrap your application with ClerkProvider. Update your app/layout.tsx file like this:


    CODE
    import type { Metadata } from 'next';
    import { ClerkProvider } from '@clerk/nextjs';

    ...

    export default function RootLayout({
    children,
    }: Readonly<{
    children: React.ReactNode;
    }>) {
    return (
    <ClerkProvider>
    <html lang="en">
    <body className="text-white bg-purple antialiased">{children}</body>
    </html>
    </ClerkProvider>
    );
    }








Creating Sign-Up and Sign-In Pages



Now, we need to set up sign-up and sign-in pages using Clerk's <SignUp /> and <SignIn /> components. These components come with built-in UI and handle all the authentication logic.



Here's how to add the pages:





  1. Set Authentication URLs: Clerk's <SignUp /> and <SignIn /> components need to know where they're mounted in your app. Add these routes to your .env.local file:


    CODE
    NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
    NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up




  2. Create the Sign-Up Page: Create a sign-up page at app/sign-up/[[...sign-up]]/page.tsx, and add the following code:


    CODE
    import { SignUp } from '@clerk/nextjs';

    export default function Page() {
    return (
    <div className="sm:w-svw sm:h-svh bg-purple w-full h-full flex items-center justify-center">
    <SignUp />
    </div>
    );
    }




  3. Create the Sign-In Page: Create a page.tsx file in the app/sign-in/[[...sign-in]] directory and add the code below:


    CODE
    import { SignIn } from '@clerk/nextjs';

    export default function Page() {
    return (
    <div className="w-svw h-svh bg-purple flex items-center justify-center">
    <SignIn />
    </div>
    );
    }




  4. Add Your Clerk Middleware: Clerk comes with a clerkMiddleware() helper that integrates authentication into our Next.js project. We can use this middleware to protect some routes while keeping others public.



    In our case, we want only the sign-up and sign-in routes accessible to everyone while protecting other routes. To do this, create a middleware.ts file in the src directory with the following code:


    CODE
    import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

    const isPublicRoute = createRouteMatcher([
    '/sign-in(.*)',
    '/sign-up(.*)',
    ]);

    export default clerkMiddleware(async (auth, request) => {
    if (!isPublicRoute(request)) {
    await auth.protect();
    }
    });

    export const config = {
    matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
    ],
    };










Creating a Workspace






Building the Create Workspace API



To allow users to create a new workspace, we need to build an API that will handle the creation process and a user interface where they can provide the necessary details.



Create a /api/workspaces/create directory, then add a route.ts file with the following code:




CODE
import { NextResponse } from 'next/server';
import { auth, currentUser } from '@clerk/nextjs/server';

import prisma from '@/lib/prisma';
import {
generateChannelId,
generateToken,
generateWorkspaceId,
isEmail,
} from '@/lib/utils';

export async function POST(request: Request) {
const { userId } = await auth();

if (!userId) {
return NextResponse.json(
{ error: 'Authentication required' },
{ status: 401 }
);
}

try {
const user = await currentUser();
const userEmail = user?.primaryEmailAddress?.emailAddress;

const body = await request.json();
const { workspaceName, channelName, emails, imageUrl } = body;

// Validate input
if (
!workspaceName ||
!channelName ||
!Array.isArray(emails) ||
emails.length === 0
) {
return NextResponse.json(
{ error: 'Invalid input data' },
{ status: 400 }
);
}

// Validate emails
for (const email of emails) {
if (!isEmail(email)) {
return NextResponse.json(
{ error: `Invalid email address: ${email}` },
{ status: 400 }
);
}
}

// Create workspace
const workspace = await prisma.workspace.create({
data: {
id: generateWorkspaceId(),
name: workspaceName,
image: imageUrl || null,
ownerId: userId,
},
});

// Create initial channel
const channel = await prisma.channel.create({
data: {
id: generateChannelId(),
name: channelName,
workspaceId: workspace.id,
},
});

// Add authenticated user as admin
await prisma.membership.create({
data: {
userId: userId,
email: userEmail!,
workspace: {
connect: { id: workspace.id },
},
role: 'admin',
},
});

// Invite provided emails
const invitations = [];
const skippedEmails = [];
const errors = [];

for (const email of emails) {
try {
// Check if an invitation already exists
const existingInvitation = await prisma.invitation.findFirst({
where: {
email,
workspaceId: workspace.id,
acceptedAt: null,
},
});

// check if the user is already a member
const existingMembership = await prisma.membership.findFirst({
where: {
email,
workspaceId: workspace.id,
},
});

if (existingInvitation) {
skippedEmails.push(email);
continue;
}

if (existingMembership) {
skippedEmails.push(email);
continue;
}

if (email === userEmail) {
skippedEmails.push(email);
continue;
}

// Generate token
const token = generateToken();

// Create invitation
const invitation = await prisma.invitation.create({
data: {
email,
token,
workspaceId: workspace.id,
invitedById: userId,
},
});

invitations.push(invitation);
} catch (error) {
console.error(`Error inviting ${email}:`, error);
errors.push({ email, error });
}
}

// Return response
const response = {
message: 'Workspace created successfully',
workspace: {
id: workspace.id,
name: workspace.name,
},
channel: {
id: channel.id,
name: channelName,
},
invitationsSent: invitations.length,
invitationsSkipped: skippedEmails.length,
errors,
};

if (errors.length > 0) {
return NextResponse.json(response, { status: 207 });
} else {
return NextResponse.json(response, { status: 200 });
}
} catch (error) {
console.error('Error creating workspace:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}






Here’s what’s going on in this API:




  • Authentication: It first checks if the user is logged in. Only logged-in users can create a workspace.


  • Input Validation: It ensures the provided information, like the workspace name, channel name, and email list, is correct.


  • Creating a Workspace and Channel: The API then creates a new workspace in the database and sets up the first channel for the workspace.


  • Adding Admin: We add the user who creates the workspace as an admin of that workspace.


  • Sending Invitations: It sends invitations to the provided email addresses while skipping any that are already invited, already members, or are not valid.




Finally, the API returns a response with details about the new workspace, channel, and how many invitations were successfully sent or skipped.






Building the Workspace Setup Page



Next, let's create a page where users can fill out the information needed to set up a new workspace. This page will be the user interface for interacting with our API.



Create a get-started directory inside /app, then create a page.tsx file in it and add the following code:




CODE
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';

import { isUrl } from '@/lib/utils';
import ArrowDropdown from '@/components/icons/ArrowDropdown';
import Avatar from '@/components/Avatar';
import Button from '@/components/Button';
import Hash from '@/components/icons/Hash';
import Home from '@/components/icons/Home';
import MoreHoriz from '@/components/icons/MoreHoriz';
import RailButton from '@/components/RailButton';
import SidebarButton from '@/components/SidebarButton';
import Tags from '@/components/Tags';
import TextField from '@/components/TextField';

const pattern = `(http)?s?:?(\/\/[^"']*\.(?:png|jpg|jpeg|gif|png|svg))`;

const GetStarted = () => {
const router = useRouter();
const [workspaceName, setWorkspaceName] = useState('');
const [channelName, setChannelName] = useState('');
const [emails, setEmails] = useState<string[]>([]);
const [imageUrl, setImageUrl] = useState('');
const [loading, setLoading] = useState(false);

const allFieldsValid = Boolean(
workspaceName &&
channelName &&
(!imageUrl || (isUrl(imageUrl) && RegExp(pattern).test(imageUrl))) &&
emails.length > 0
);

const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
if (allFieldsValid) {
e.stopPropagation();

try {
setLoading(true);
const response = await fetch('/api/workspaces/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
workspaceName: workspaceName.trim(),
channelName: channelName.trim(),
emails,
imageUrl,
}),
});

const result = await response.json();

if (response.ok) {
alert('Workspace created successfully!');
const { workspace, channel } = result;
router.push(`/client/${workspace.id}/${channel.id}`);
} else {
alert(`Error: ${result.error}`);
}
} catch (error) {
console.error('Error creating workspace:', error);
alert('An unexpected error occurred.');
} finally {
setLoading(false);
}
}
};

return (
<div className="client font-lato w-screen h-screen flex flex-col">
<div className="absolute w-full h-full bg-theme-gradient" />
<div className="relative w-full h-10 flex items-center justify-between pr-1"></div>
<div className="w-screen h-[calc(100svh-40px)] grid grid-cols-[70px_auto]">
<div className="hidden relative w-[4.375rem] sm:flex flex-col items-center overflow-hidden gap-3 pt-2 z-[1000] bg-transparent">
<div className="w-9 h-9 mb-[5px]">
<Avatar
width={36}
borderRadius={8}
fontSize={20}
fontWeight={600}
data={{ name: workspaceName, image: imageUrl }}
/>
</div>
<div className="relative flex flex-col items-center w-[3.25rem]">
<div className="relative">
<RailButton
title="Home"
icon={<Home color="var(--primary)" filled />}
active
/>
<div className="absolute w-full h-full top-0 left-0" />
</div>
<div className="relative opacity-30">
<RailButton
title="More"
icon={<MoreHoriz color="var(--primary)" />}
/>
<div className="absolute w-full h-full top-0 left-0" />
</div>
</div>
</div>
<div className="relative w-svw h-full sm:h-auto sm:w-auto flex mr-1 mb-1 rounded-md overflow-hidden border border-solid border-[#797c814d]">
<div className="hidden w-[275px] relative px-2 sm:flex flex-col flex-shrink-0 gap-3 min-w-0 min-h-0 max-h-[calc(100svh-44px)] bg-[#10121499] border-r-[1px] border-solid border-r-[#797c814d]">
<div className="pl-1 w-full h-[49px] flex items-center justify-between">
<div className="max-w-[calc(100%-80px)]">
<div className="w-fit max-w-full rounded-md py-[3px] px-2 flex items-center text-white hover:bg-hover-gray">
<span className="truncate text-[18px] font-[900] leading-[1.33334]">
{workspaceName}
</span>
</div>
</div>
</div>
{channelName && (
<div className="w-full flex flex-col">
<div className="h-7 -ml-1.5 flex items-center px-4 text-[15px] leading-7">
<button className="hover:bg-hover-gray rounded-md">
<ArrowDropdown color="var(--icon-gray)" />
</button>
<button className="flex px-[5px] max-w-full rounded-md text-sidebar-gray font-medium hover:bg-hover-gray">
Channels
</button>
</div>
<SidebarButton icon={Hash} title={channelName} />
</div>
)}
<div className="absolute w-full h-full top-0 left-0" />
</div>
<div className="bg-[#1a1d21] grow p-16 flex flex-col">
<div className="max-w-[705px] flex flex-col gap-8">
<h2 className="max-w-[632px] font-sans font-bold mb-2 text-[45px] leading-[46px] text-white">
Create a new workspace
</h2>
<form onSubmit={onSubmit} action={() => {}} className="contents">
<TextField
label="Workspace name"
name="workspaceName"
value={workspaceName}
onChange={(e) => setWorkspaceName(e.target.value)}
placeholder="Enter a name for your workspace"
required
/>
<TextField
label={
<span>
Workspace image{' '}
<span className="text-[#9a9b9e] ml-0.5">(optional)</span>
</span>
}
name="workspaceImage"
type="url"
value={imageUrl}
onChange={(e) => setImageUrl(e.target.value)}
placeholder="Paste an image URL"
pattern={`(http)?s?:?(\/\/[^"']*\.(?:png|jpg|jpeg|gif|png|svg))`}
title='Image URL must start with "http://" or "https://" and end with ".png", ".jpg", ".jpeg", ".gif", or ".svg"'
/>
<TextField
label="Channel name"
name="channelName"
value={channelName}
onChange={(e) =>
setChannelName(
e.target.value.toLowerCase().replace(/\s/g, '-')
)
}
placeholder="Enter a name for your first channel"
maxLength={80}
required
/>
<Button
type="submit"
disabled={emails.length === 0}
className="w-fit order-5 capitalize py-2 hover:bg-[#592a5a] hover:border-[#592a5a]"
loading={loading}
>
Submit
</Button>
</form>
<Tags
values={emails}
setValues={setEmails}
label="Invite members"
placeholder="Enter email addresses"
/>
</div>
</div>
</div>
</div>
</div>
);
};

export default GetStarted;






In the code above:





  • The page allows users to provide details for creating a new workspace, including:





    • Workspace Name: The name of the workspace.


    • Channel Name: The name of the first channel to be created within the workspace.


    • Emails of Members to Invite: Users can enter a list of email addresses to invite members to the workspace.


    • Workspace Image (Optional): Users can optionally provide an image URL to represent the workspace.






  • The form uses the useState hook to store the values the user enters and uses allFieldsValid to ensure all required fields are filled in correctly.



  • When the form is submitted, it makes a POST request to the /api/workspaces/create route, passing along the workspace details. If the request is successful, we redirect the user to the new workspace.



  • The interface provides visual feedback while the request is processed, such as showing a loading state or error messages if something goes wrong.







By following these steps, you can confirm that the workspace creation flow functions correctly.






Setting Up Stream In Your Application






What is Stream?



and



To start using Stream, you'll need to create an account:




  1. Sign Up: Go to the



    After creating your Stream account, the next step is to set up an app for your project:




    1. Create a New App: In your Stream dashboard, click the "Create App" button.


    2. Configure Your App:





    CODE
    * **App Name**: Enter a name like "**Slack Clone**" or any other name you choose.

    * **Region**: Pick the region nearest to you for the best performance.

    * **Environment**: Keep it set to "**Development**".

    * Click the "**Create App**" to finish.






    1. Get Your API Keys: After creating the app, navigate to the "App Access Keys" section. You’ll need these keys to connect Stream to your project.



      :






      CODE
      // app/layout.tsx
      ...
      import '@stream-io/video-react-sdk/dist/css/styles.css';
      import 'stream-chat-react/dist/css/v2/index.css';
      import './globals.css';
      ...








    Syncing Clerk with Your Stream App



    To make sure user data is consistent between Clerk and Stream, you need to set up a webhook that syncs user information:





    1. Set Up ngrok: Since webhooks require a publicly accessible URL, we'll use ngrok to expose our local server. Follow the steps below to set up an ngrok tunnel for your app:




    CODE
    * Go to the [ngrok website](https://dashboard.ngrok.com/signup) and sign up for a free account.

    * [Download and install ngrok](https://dashboard.ngrok.com/get-started/setup), then start a tunnel to your local server (assuming it's running on port 3000):







    CODE
        ```bash
    ngrok http 3000 --domain=YOUR_DOMAIN
    ```







    CODE
        Replace `YOUR_DOMAIN` with the [generated ngrok domain](https://dashboard.ngrok.com/cloud-edge/domains).





    1. Create a Webhook Endpoint in Clerk:




    CODE
    * **Navigate to Webhooks**: In your [Clerk dashboard](https://dashboard.clerk.com/last-active?path=webhooks), navigate to the “**Configure**” tab and select "**Webhooks**.”

    * **Add a New Endpoint**:

    * Click "**Add Endpoint**" and enter your ngrok URL, followed by `/api/webhooks` (e.g., `https://your-subdomain.ngrok.io/api/webhooks`).

    * Under “**Subscribe to events**”, select `user.created` and `user.updated`.

    * Click "**Create**".

    * **Get the Signing Secret**: Copy the signing secret provided and add it to your `.env.local` file:







    CODE
        ```dockerfile
    WEBHOOK_SECRET=your_clerk_webhook_signing_secret
    ```







    CODE
        Replace `your_clerk_webhook_signing_secret` with the signing secret from the webhooks page.

    ![Signing secret](https://cdn.hashnode.com/res/hashnode/image/upload/v1726741867125/7c6ffd89-36ac-4c4b-a5e0-bd665796612d.png)






    1. Install Svix: We need






      Setting Up the Client Page



      The Client component is a utility page that redirects users to their last active workspace and channel. It does this by checking the activitySession stored in localStorage. If no activity session is found, the user is redirected to the homepage.



      Create a page.tsx file in the /app/client directory with the following code:




      CODE
      'use client';
      import { useRouter } from 'next/navigation';
      import { useEffect } from 'react';

      export default function Client() {
      const router = useRouter();

      useEffect(() => {
      const fetchActivitySession = async () => {
      const activitySession = localStorage.getItem('activitySession');
      if (activitySession) {
      const { workspaceId, channelId } = await JSON.parse(activitySession);
      router.push(`/client/${workspaceId}/${channelId}`);
      } else {
      router.push('/');
      }
      };

      fetchActivitySession();
      }, [router]);

      return null;
      }









      Building the Workspace Page



      Finally, we will create a second utility page which will handle the logic of redirecting the user to a channel within a workspace.



      Create a page.tsx file in the /client/[workspaceId] directory:




      CODE
      'use client';
      import { useContext, useEffect } from 'react';
      import { useRouter } from 'next/navigation';

      import { AppContext, Workspace } from '../layout';

      interface WorkspacePageProps {
      params: {
      workspaceId: string;
      };
      }

      export default function WorkspacePage({ params }: WorkspacePageProps) {
      const { workspaceId } = params;
      const { workspace, setWorkspace, setOtherWorkspaces } =
      useContext(AppContext);
      const router = useRouter();

      useEffect(() => {
      const goToChannel = (workspace: Workspace) => {
      const channelId = workspace.channels[0].id;
      localStorage.setItem(
      'activitySession',
      JSON.stringify({ workspaceId: workspace.id, channelId })
      );
      router.push(`/client/${workspace.id}/${channelId}`);
      };

      const loadWorkspace = async () => {
      try {
      const response = await fetch(`/api/workspaces/${workspaceId}`);
      const result = await response.json();
      if (response.ok) {
      setWorkspace(result.workspace);
      setOtherWorkspaces(result.otherWorkspaces);
      goToChannel(result.workspace);
      } else {
      console.error('Error fetching workspace data:', result.error);
      }
      } catch (error) {
      console.error('Error fetching workspace data:', error);
      }
      };

      if (!workspace) {
      loadWorkspace();
      } else {
      goToChannel(workspace);
      }
      }, [workspace, workspaceId, setWorkspace, setOtherWorkspaces, router]);

      return null;
      }






      In the code above, if the workspace data isn't loaded yet, we fetch it from the /api/workspaces/[workspaceId] route, and navigate the user to the first available channel in that workspace.



      and Clerk.


    2. Created API routes for managing workspaces and channels.


    3. Built essential components for navigating between workspaces and channels.




    4. In the next part, we will focus on implementing real-time messaging and managing channels.



      Stay tuned!

      Vollständiger Original-Bericht
      Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
      ↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
The Gemini desktop app is now available for Windows
1 Quelle
Windows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC
1 Quelle
Vorsicht: Android-Malware verschlüsselt Ihre Handys und nimmt heimlich Fotos auf
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Building a Slack Clone with Next.js and TailwindCSS - Part One

Thematisch verwandte Begriffe: Building, Slack, Clone, with · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...