WebSocket Streaming Not Working in Django + Next.js — Only Getting First and Final Message

I'm building a ai chat app with Django (Channels) for the backend and Next.js for the frontend. The goal is to stream AI-generated responses chunk by chunk over a WebSocket connection — similar to how a REST API with ReadableStream works. However, on the frontend, Its only displaying the first chunk then final completed message, not the intermediate streaming chunks.

Here’s a simplified version of my setup:

Backend (Django WebSocket Consumer): I’m using an async generator (handle_chat_request) to produce message chunks.

async def receive(self, text_data):
    logging.info(f"WebSocket message received: {text_data}")

    try:
        data = json.loads(text_data)
        message_type = data.get("type")

        if message_type == "message":
            jwt_token = data.get("token")
            if not jwt_token:
                await self.send(json.dumps({"type": "error", "message": "Missing Authorization token."}))
                await self.close(code=4001)
                return

            user_message = data.get("content")
            user_id = data.get("user_id")

            combined_response = ""

            # Stream response chunks
            async for chunk in handle_chat_request(user_message, user_id, jwt_token):
                combined_response += chunk
                await self.send(json.dumps({"type": "ai_response_chunk", "content": chunk}))
                await asyncio.sleep(0)  # Yield control to allow chunk sending

            # Send final complete message
            await self.send(json.dumps({"type": "ai_response_complete", "content": combined_response}))
        
    except Exception as e:
        logging.error(f"WebSocket error: {e}")
        await self.send(json.dumps({"type": "error", "message": "An error occurred."}))

Frontend (Next.js WebSocket Client): I’m listening for incoming chunks and trying to append them to the latest bot message:

const handleWebSocketMessage = (event) => {
  try {
    const data = JSON.parse(event.data);

    switch (data.type) {
      case "ai_response_chunk":
        setMessages((messages) => {
          const updatedMessages = [...messages];
          const lastMessageIndex = updatedMessages.findLastIndex(
            (m) => m.role === "bot"
          );
          
          if (lastMessageIndex !== -1) {
            updatedMessages[lastMessageIndex] = {
              ...updatedMessages[lastMessageIndex],
              content: updatedMessages[lastMessageIndex].content + data.content,
            };
          } else {
            updatedMessages.push({ role: "bot", content: data.content });
          }

          return updatedMessages;
        });
        break;

      case "ai_response_complete":
        //some other functionality
        break;

      case "error":
        setError(data.message);
        setLoading(false);
        break;
    }
  } catch (error) {
    console.error("Error parsing WebSocket message:", error);
    setLoading(false);
  }
};

Problem: The "ai_response_chunk" are ariving as expected by the UI only displays the first chunk then the final message content when it is complete, but I don’t see incremental updates like I do with the REST API implementation.

Rest Implementation:

  fetch(`/api/chat`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ message: userMessage }),
        }).then(async (response) => {
          if (!response.ok) throw new Error("Chat API failed");
          if (!response.body) throw new Error("ReadableStream not supported");

          const reader = response.body.getReader();
          const decoder = new TextDecoder();
          let done = false;
          let botMessage = "";

          // Add bot message placeholder
          setMessages((messages) => [
            ...messages,
            { content: "", role: "bot" },
          ]);

          while (!done) {
            const { value, done: doneReading } = await reader.read();
            done = doneReading;
            const chunk = decoder.decode(value, { stream: true });
            botMessage += chunk;

            setMessages((messages) => {
              const updatedMessages = [...messages];
              const lastMessageIndex = updatedMessages.findLastIndex(
                (m) => m.role === "bot"
              );
              if (lastMessageIndex !== -1) {
                updatedMessages[lastMessageIndex] = {
                  ...updatedMessages[lastMessageIndex],
                  content: updatedMessages[lastMessageIndex].content + chunk,
                };
              }
              return updatedMessages;
            });
          }
    ```

You can add a small delay inside the chunk-sending loop! For example, using asyncio.sleep will help control the flow.

for chunk in self.sync_stream_chat_response(user_message, user_id, jwt_token):
    chunk_count += 1
    print(f"Sending chunk #{chunk_count} to WebSocket")
    await self.send(json.dumps({"type": "ai_response_chunk", "content": chunk}))
    
    # Add a small delay (e.g., 50ms)
    await asyncio.sleep(0.05)

await self.send(json.dumps({"type": "ai_response_complete"}))

await asyncio.sleep(0.05) → Pauses for 50 milliseconds between each chunk.

Вернуться на верх