Next.js与Supabase实时聊天:构建即时通讯应用

Next.js与Supabase实时聊天:构建即时通讯应用

【免费下载链接】next.js The React Framework 【免费下载链接】next.js 项目地址: https://gitcode.com/GitHub_Trending/next/next.js

你是否正在寻找一种简单高效的方式来构建实时聊天功能?Next.js作为React框架提供了强大的服务端渲染和客户端交互能力,而Supabase则提供了实时数据库和身份验证功能。本文将带你一步步实现一个功能完整的实时聊天应用,无需复杂的后端知识,只需基本的JavaScript和React基础。

准备工作

在开始之前,我们需要准备以下环境和工具:

  • Node.js 18.x或更高版本
  • npm、yarn或pnpm包管理器
  • 一个Supabase账号(可以在Supabase官网免费注册)
  • Git(用于克隆项目模板)

首先,我们需要获取Next.js与Supabase集成的基础项目。可以通过以下命令克隆官方示例项目:

git clone https://gitcode.com/GitHub_Trending/next/next.js.git
cd next.js/examples/with-supabase

这个示例项目包含了Next.js与Supabase集成的基本配置,我们将在此基础上构建聊天功能。项目结构详情可参考examples/with-supabase目录。

项目设置

安装依赖

进入项目目录后,安装所需的依赖包:

npm install
# 或
yarn install
# 或
pnpm install

配置环境变量

复制环境变量示例文件并修改为你自己的Supabase项目信息:

cp .env.example .env.local

打开.env.local文件,添加你的Supabase项目URL和匿名密钥,这些信息可以在Supabase项目控制台的设置 > API中找到:

NEXT_PUBLIC_SUPABASE_URL=你的Supabase项目URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=你的Supabase匿名密钥

数据库设计

在Supabase控制台中,我们需要创建一个用于存储聊天消息的表。登录Supabase控制台,进入SQL编辑器,执行以下SQL语句:

CREATE TABLE messages (
  id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
  content TEXT NOT NULL,
  user_id UUID NOT NULL REFERENCES auth.users(id),
  username TEXT NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
);

-- 启用实时订阅
ALTER PUBLICATION supabase_realtime ADD TABLE messages;

-- 创建索引以提高查询性能
CREATE INDEX idx_messages_created_at ON messages(created_at);

这段SQL创建了一个messages表,包含消息内容、发送用户ID、用户名和创建时间等字段,并启用了实时订阅功能,这是实现实时聊天的关键。

构建聊天界面

创建聊天页面

app目录下创建一个chat目录,并添加page.tsx文件:

'use client';

import { useState, useEffect } from 'react';
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import { AuthButton } from '@/components/auth-button';

export default function ChatPage() {
  const [messages, setMessages] = useState([]);
  const [newMessage, setNewMessage] = useState('');
  const [user, setUser] = useState(null);
  const supabase = createClientComponentClient();

  // 获取当前用户信息
  useEffect(() => {
    const getCurrentUser = async () => {
      const { data: { session } } = await supabase.auth.getSession();
      if (session) {
        setUser(session.user);
      }
    };

    getCurrentUser();
  }, [supabase.auth]);

  // 加载历史消息
  useEffect(() => {
    const fetchMessages = async () => {
      const { data } = await supabase
        .from('messages')
        .select('*')
        .order('created_at', { ascending: true });
      
      if (data) setMessages(data);
    };

    fetchMessages();
  }, [supabase]);

  // 订阅新消息
  useEffect(() => {
    if (!user) return;

    const subscription = supabase
      .channel('public:messages')
      .on(
        'postgres_changes',
        { event: 'INSERT', schema: 'public', table: 'messages' },
        (payload) => {
          setMessages((prev) => [...prev, payload.new]);
        }
      )
      .subscribe();

    return () => {
      supabase.removeChannel(subscription);
    };
  }, [supabase, user]);

  // 发送消息
  const handleSendMessage = async (e) => {
    e.preventDefault();
    if (!newMessage.trim() || !user) return;

    await supabase
      .from('messages')
      .insert([
        { 
          content: newMessage, 
          user_id: user.id,
          username: user.email.split('@')[0] // 使用邮箱前缀作为用户名
        }
      ]);

    setNewMessage('');
  };

  return (
    <div className="max-w-2xl mx-auto p-4">
      <div className="flex justify-between items-center mb-6">
        <h1 className="text-2xl font-bold">实时聊天</h1>
        <AuthButton />
      </div>
      
      {!user ? (
        <div className="text-center py-10">
          <p>请先登录以参与聊天</p>
        </div>
      ) : (
        <>
          <div className="border border-gray-200 rounded-lg h-[500px] overflow-y-auto p-4 mb-4 bg-white">
            {messages.map((message) => (
              <div 
                key={message.id} 
                className={`mb-4 p-3 rounded-lg ${
                  message.user_id === user.id ? 'bg-blue-50 ml-auto' : 'bg-gray-50'
                } max-w-[80%]`}
              >
                <div className="text-sm font-medium text-gray-500 mb-1">
                  {message.username}
                </div>
                <p className="text-gray-800">{message.content}</p>
                <div className="text-xs text-gray-400 mt-1 text-right">
                  {new Date(message.created_at).toLocaleTimeString()}
                </div>
              </div>
            ))}
          </div>
          
          <form onSubmit={handleSendMessage} className="flex gap-2">
            <input
              type="text"
              value={newMessage}
              onChange={(e) => setNewMessage(e.target.value)}
              placeholder="输入消息..."
              className="flex-1 p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
            <button
              type="submit"
              className="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition-colors"
            >
              发送
            </button>
          </form>
        </>
      )}
    </div>
  );
}

关键功能解析

上面的代码实现了几个核心功能:

  1. 用户认证:通过AuthButton组件实现用户登录/注销功能,项目中已有此组件,无需额外编写。

  2. 消息加载:页面加载时从Supabase数据库获取历史消息。

  3. 实时订阅:使用Supabase的实时订阅功能,当有新消息插入时自动更新消息列表。

  4. 消息发送:将用户输入的消息发送到Supabase数据库。

  5. 消息显示:根据消息发送者显示不同的样式,区分自己和他人的消息。

运行应用

完成以上步骤后,启动开发服务器:

npm run dev
# 或
yarn dev
# 或
pnpm dev

在浏览器中访问http://localhost:3000/chat,你应该能看到聊天界面了。使用Supabase的认证功能登录后,就可以与其他用户进行实时聊天了。

功能扩展建议

这个基础聊天应用可以进一步扩展,添加更多功能:

  1. 私聊功能:添加用户列表和私聊功能,只需添加一个chat_rooms表和修改实时订阅逻辑。

  2. 消息删除/编辑:添加消息管理功能,需要更新表结构和权限设置。

  3. 文件/图片发送:利用Supabase Storage实现文件上传功能。

  4. 消息通知:添加浏览器通知功能,当收到新消息时提醒用户。

  5. 在线状态指示:添加用户在线状态显示,可通过Supabase的Presence功能实现。

总结

通过本文的教程,你已经学会了如何使用Next.js和Supabase构建一个简单但功能完整的实时聊天应用。关键技术点包括:

  • Next.js的App Router和客户端组件
  • Supabase的实时数据库订阅功能
  • 用户认证与权限控制
  • React状态管理与副作用处理

这个应用虽然简单,但展示了现代Web应用开发的核心概念和最佳实践。你可以基于这个基础继续扩展,构建更复杂的通讯系统。

官方文档和更多示例可以参考:

【免费下载链接】next.js The React Framework 【免费下载链接】next.js 项目地址: https://gitcode.com/GitHub_Trending/next/next.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值