Engineering

Building a Custom Chat Widget with Discord and Cloudflare Workers: Why We Ditched Intercom, Crisp, and the Rest

admin

Every consultancy needs a way for potential clients to reach out. Live chat widgets have become the standard - they sit in the corner of your website, ready to capture leads and answer questions. The problem? Most of them are destroying your page performance.

This is the story of how we went from a 74 Lighthouse performance score to a perfect 100 by replacing bloated third-party chat widgets with our own custom solution built on Cloudflare Workers and Discord.

The Performance Problem We Could Not Ignore

When we rebuilt our website with Astro, performance was the primary goal. We achieved a perfect Lighthouse score across all metrics. Then we added a chat widget, and everything changed.

We tried them all:

  • Intercom - The industry standard. Also the heaviest. Our performance score dropped from 100 to 74. The initial JavaScript bundle alone was over 400KB, with additional assets loading asynchronously.

  • Crisp - Marketed as a lightweight alternative. Still added 200KB+ of JavaScript and several network requests that blocked rendering.

  • HubSpot Chat - Came bundled with our CRM integration. Added even more weight with tracking scripts and analytics.

  • Tawk.to - Free option, but “free” came with the cost of slow load times and aggressive resource loading.

  • Rocket.Chat - Open-source and self-hosted. Required significant infrastructure just to serve a chat widget.

Every solution followed the same pattern: load a massive JavaScript bundle, establish connections to multiple third-party servers, inject styles that sometimes conflicted with our design, and tank our Core Web Vitals.

For a DevOps consulting company that preaches performance optimisation to clients, having a slow website was not acceptable.

The Realisation: We Were Overcomplicating This

Chat widgets have become bloated because vendors keep adding features most teams never use. AI chatbots. Visitor tracking. Screen sharing. Knowledge base integrations. CRM syncing. Marketing automation.

We stepped back and asked: what do we actually need?

  1. A visitor fills in their name and email
  2. They type a message
  3. Our team sees the message and responds
  4. The visitor receives the response in real-time

That was it. Four requirements. We did not need AI. We did not need visitor behaviour tracking. We did not need complex routing rules. We just needed a simple, fast way to have conversations.

And then it hit us: we already had a tool perfect for real-time team communication. Discord.

Why Discord Makes Perfect Sense

Our team already lived in Discord. It is where we coordinate on projects, share knowledge, and communicate throughout the day. Using Discord as the backend for our chat system meant:

  • Zero additional tools for the team - No new dashboard to learn, no new notifications to manage
  • Mobile support out of the box - Discord mobile apps meant we could respond from anywhere
  • Thread organisation - Each chat session becomes a Discord thread, keeping conversations organised
  • History and search - All conversations are searchable within Discord
  • Free for our use case - No per-seat pricing, no message limits

The question became: how do we connect a website chat widget to Discord without adding third-party bloat?

The Architecture: Astro + Cloudflare Workers + Discord

We designed a system with three components:

Website Chat Widget <--> WebSocket <--> Cloudflare Worker <--> Discord API
                                              ^
                                              |
                                        Discord Bot Relay
                                              |
                                           Discord

Component 1: The Chat Widget (Astro Component)

A pure vanilla JavaScript chat widget embedded directly in our Astro site. No framework dependencies. No external scripts. Just clean, minimal code that:

  • Renders a floating chat button
  • Opens a chat window when clicked
  • Establishes a WebSocket connection to our Cloudflare Worker
  • Handles message sending and receiving

The entire widget adds approximately 5KB to our bundle - compared to 200KB+ from third-party solutions. That is a 97.5% reduction in chat widget size.

Component 2: Cloudflare Worker with Durable Objects

Cloudflare Workers provide serverless compute at the edge. Durable Objects add persistent state and WebSocket support. Together, they handle:

  • WebSocket connections from website visitors
  • Session management and thread reuse
  • Message routing between visitors and Discord
  • Discord API calls to create threads and send messages

The Worker runs globally with zero cold starts and automatic scaling. We do not manage any servers. We do not pay for idle capacity. We just pay for actual usage, which for a chat system amounts to almost nothing.

Component 3: Discord Bot Relay

A lightweight Node.js bot also running on Cloudflare Workers that:

  • Listens for messages in support threads
  • Relays agent responses back to the Cloudflare Worker
  • Routes messages to the correct visitor session

The bot consumes minimal resources - it is essentially just forwarding messages between Discord and the main Worker.

How It Works: A Complete Flow

When a visitor opens the chat:

  1. Widget initialises - Establishes a WebSocket connection to our Cloudflare Worker
  2. Visitor enters details - Name and email are collected
  3. Thread lookup - The Worker checks if this email has chatted before (within 90 days)
  4. Thread creation or reuse - Either creates a new Discord thread or reconnects to an existing one
  5. Messages flow - Visitor messages go to Discord, agent responses come back through the relay

The smart thread reuse feature means returning visitors continue their previous conversation. No more repeating context. Support agents see the full history. It creates a significantly better experience than most enterprise chat solutions provide.

Security with Cloudflare Turnstile

We integrated Cloudflare Turnstile - Cloudflare’s privacy-focused CAPTCHA alternative - to prevent spam and abuse. Before initiating a chat session, visitors complete an invisible challenge that verifies they are human without the friction of traditional CAPTCHAs.

All user input is sanitised before reaching Discord. Message length limits prevent abuse. CORS validation ensures only our domain can connect. WebSocket connections require proper initialisation sequences.

Automatic Reconnection and Session Persistence

Real-world users experience connection drops, page refreshes, and browser restarts. Our widget handles all of these:

  • Connection drops - Automatic reconnection with exponential backoff (2s, 4s, 8s intervals)
  • Page refresh - Session restored from localStorage within a one-hour window
  • Returning visitors - Thread reused within 90 days based on email

The Worker uses Durable Objects to persist session state. Even if our Worker instance restarts, conversations continue seamlessly.

The Results: Perfect Performance Restored

After deploying our custom chat solution:

  • Lighthouse Performance: 100 (was 74 with Intercom)
  • First Contentful Paint: 0.8s (was 1.6s)
  • Total Blocking Time: 0ms (was 310ms)
  • Chat Widget Bundle Size: ~5KB (was 400KB+)
  • Third-party Requests: 0 (was 12+)

More importantly, the chat actually works better. Our team responds faster because messages arrive in Discord where we already live. Visitors get a smoother experience because the widget loads instantly. Thread continuity means no repeated explanations.

The Technical Implementation

For those interested in the technical details, here is how the core pieces fit together.

WebSocket Protocol

Messages follow a simple JSON protocol:

// Client initialises session
{
  "type": "init",
  "data": {
    "name": "John Doe",
    "email": "john@example.com",
    "page": "/pricing"
  }
}

// Client sends message
{
  "type": "message",
  "data": {
    "message": "I need help with your services"
  }
}

// Server confirms ready state
{
  "type": "ready",
  "data": {
    "message": "Connected to support. An agent will be with you shortly."
  }
}

Durable Object Session Management

Each chat session maps to a Durable Object that maintains:

  • WebSocket connection state
  • Discord thread ID
  • User information (name, email)
  • Session timestamps for reconnection

The Durable Object handles hibernation gracefully - sessions can survive Worker restarts because state is persisted to Cloudflare’s edge storage.

Discord Thread Creation

When a new visitor initiates a chat, the Worker calls the Discord API to create a thread:

const thread = await fetch(
  `https://discord.com/api/v10/channels/${SUPPORT_CHANNEL_ID}/threads`,
  {
    method: "POST",
    headers: {
      Authorization: `Bot ${BOT_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: `Support: ${userName}`,
      auto_archive_duration: 60,
      type: 11, // Public thread
    }),
  }
);

The thread name includes the visitor’s name for easy identification. Auto-archive keeps the channel clean while preserving history.

What We Learned

Building this system reinforced several principles we apply in our cloud consulting work:

  1. Simplicity wins - We removed 95% of features we never used and got a better product
  2. Use tools you already have - Discord was already in our workflow; adding it as infrastructure cost nothing
  3. Edge computing changes the game - Cloudflare Workers let us deploy globally without managing infrastructure
  4. Measure everything - Lighthouse scores kept us honest about performance impact
  5. Build for failure - Automatic reconnection and session persistence handle real-world conditions

Should You Build Your Own?

Not necessarily. If you need AI chatbots, complex routing, or deep CRM integrations, commercial solutions make sense. But if you need simple, fast real-time chat and you already use Discord, this approach is worth considering.

The total development time was about two days. Ongoing costs are minimal - the Cloudflare Workers free tier covers most usage. Performance impact is essentially zero.

Compare that to $50-500/month for commercial chat solutions that slow down your site and require learning new tools.

The Code

The full implementation is relatively straightforward:

  • Chat Widget: ~300 lines of vanilla JavaScript in an Astro component
  • Cloudflare Worker: ~650 lines of TypeScript handling WebSocket and Discord API
  • Discord Bot Relay: ~90 lines of JavaScript running on Cloudflare Workers

If you want us to build something similar for your team, reach out to us.

Beyond Chat: The Broader Lesson

This project exemplifies what we do at Tasrie IT Services. We look at common problems - slow websites, expensive tools, complex infrastructure - and find simpler solutions that deliver better results.

Whether it is optimising Kubernetes costs, migrating to cloud-native architectures, or automating business processes, the approach is the same: understand the actual requirements, eliminate unnecessary complexity, and build solutions that scale.

If your team is spending money on tools that slow you down, there is probably a better way. Sometimes the best solution is the one you build yourself.


We build relationships, not just technology.

Ready to optimise your infrastructure or explore custom solutions for your team? Book a 30-minute strategy call or contact us directly.

Chat with real humans
Chat on WhatsApp