For AI agents: Find a documentation index at https://talkjs.com/llms.txt (full content at https://talkjs.com/llms-full.txt). Get a markdown version of any page by appending .md to its URL path.
Skip to content

Svelte chat

Svelte chat between your users, on SvelteKit 3

A live chat in a new SvelteKit app takes three commands and two files, with TalkJS bringing the chat UI and hosting the messages. Don’t write it from habit, though: on SvelteKit 3, some of the usual code builds cleanly and then fails when the page loads. Here’s what to write instead.

// src/routes/chat/+page.ts
export const ssr = false;

<!-- src/routes/chat/+page.svelte -->
<t-chatbox
  app-id={appId}
  user-id={userId}
  conversation-id={conversationId}
></t-chatbox>
Nina
Hi Frank! Is the bike still free on Saturday?
9:15 AM
You
Yes, it’s yours. I’ll send the pickup details here.
9:16 AM
Nina
Thanks, see you then!
9:17 AM
thumbs-up
Write a reply…

What does a fresh SvelteKit 3 app do with the code you’d write from habit?

Some of it fails, and a passing build won’t tell you. Import the TalkJS web components at the top of a page and npm run build still exits 0. The page itself then returns a 500, because that import runs on the server, where there’s no browser.

The same page, after a build and after a request

PassedThe build: exit 0
$ npm run build
✓ built in 142ms
✔ done
$ echo $?
0
FailedThe page: HTTP 500
$ npm run preview
500 GET /

ReferenceError: HTMLElement
is not defined
One +page.svelte with the web components imported at the top, in a fresh sv create app. With prerender = true on the page, the build fails too.

That import isn’t the only habit that goes wrong. Here’s the list, worst first. The ones at the bottom at least warn you.

What a fresh SvelteKit 3 app did with each habit

Fails
You write: import '@talkjs/web-components' at the top of +page.svelteWhat happened: Build passes, then the 500 aboveInstead: export const ssr = false in +page.ts, or import in onMount
You write: import { KEY } from '$env/static/private'What happened: No import error. The value is undefined, and signing a token returns a 500: secretOrPrivateKey must have a valueInstead: Declare it in src/env.ts, then import from $app/env/private
You write: import Chat from '$lib/Chat.svelte'What happened: A 500: module_removed_libInstead: #lib/Chat.svelte
Runs in the wrong place
You write: getTalkSession() at the top of a page the server rendersWhat happened: No error. The server starts a TalkJS session on each render and logs ssr_fetch_eagerInstead: Call it in onMount
Works, with a warning
You write: on:sendMessage={fn}What happened: event_directive_deprecatedInstead: onsendMessage={fn}
You write: <svelte:component this={chat.default} />What happened: svelte_component_deprecatedInstead: <chat.default />, or the onMount setup
You write: import { browser } from '$app/environment'What happened: app_environment_deprecatedInstead: $app/env
SvelteKit 3.1.1, Svelte 5.57.3 and @talkjs/web-components 0.3.12, November 2026. The new template turns on runes mode, so the two deprecated syntaxes warn.

The two files that give you a live chat

Start from a new app, or add the install and the two files to the app you have.

npx sv create my-app --template minimal --types ts --no-add-ons --install npm
cd my-app && npm install @talkjs/web-components @talkjs/core
npm run dev

The first file, src/routes/chat/+page.ts, is one line that turns off server rendering for the chat route:

export const ssr = false;

The second, src/routes/chat/+page.svelte, creates two users and a conversation, then shows it:

<script lang="ts">
  import '@talkjs/web-components';
  import '@talkjs/web-components/default.css';
  import { getTalkSession } from '@talkjs/core';

  const appId = '<APP_ID>';
  const userId = 'frank';
  const otherUserId = 'nina';
  const conversationId = 'my_conversation';

  const session = getTalkSession({ appId, userId });
  session.currentUser.createIfNotExists({ name: 'Frank' });
  session.user(otherUserId).createIfNotExists({ name: 'Nina' });
  const conversation = session.conversation(conversationId);
  conversation.createIfNotExists();
  conversation.participant(otherUserId).createIfNotExists();
</script>

<t-chatbox
  style="width: 400px; height: 600px;"
  app-id={appId}
  user-id={userId}
  conversation-id={conversationId}
></t-chatbox>

From the code example in the TalkJS Svelte guide, on the versions above plus @talkjs/core 1.12.1.

SvelteKit’s docs advise against ssr = false on most pages, and they’re right about most pages. A chat page behind a login has nothing to render on the server, because the server doesn’t have the messages. Your other routes don’t pay for the chat, either: in one build, a route with no chat loaded 34 kB of gzipped JavaScript and the chat route 365 kB.

Gzipped JavaScript a route loads

No chat
No chat: 34 kB of gzipped JavaScript34 kB
With the chat
With the chat: 365 kB of gzipped JavaScript365 kB
0200 kB400
Two routes in one production build of a fresh SvelteKit 3.1.1 app, November 2026. The chat route also fetches a 14 kB emoji picker from a CDN, not counted here.

This chat has hard-coded users and authentication off, which is enough to try it. Before you launch, sign a token for each user.

When the chat shares a page with server-rendered content

Say the chat sits on an order page, and the order should still render on the server. Import the components in onMount, which doesn’t run on the server, and start the session there too:

<script lang="ts">
  import { onMount } from 'svelte';
  import { getTalkSession } from '@talkjs/core';
  import '@talkjs/web-components/default.css';

  const appId = '<APP_ID>';
  const userId = 'frank';
  const otherUserId = 'nina';
  const conversationId = 'my_conversation';

  onMount(async () => {
    await import('@talkjs/web-components');

    const session = getTalkSession({ appId, userId });
    session.currentUser.createIfNotExists({ name: 'Frank' });
    session.user(otherUserId).createIfNotExists({ name: 'Nina' });
    const conversation = session.conversation(conversationId);
    conversation.createIfNotExists();
    conversation.participant(otherUserId).createIfNotExists();
  });
</script>

<h1>Order 1234</h1>
<t-chatbox
  style="width: 400px; height: 600px;"
  app-id={appId}
  user-id={userId}
  conversation-id={conversationId}
></t-chatbox>

We’d pick this over the other two ways that keep server rendering on. {#if browser} works too, but it needs a SvelteKit import, and that import was renamed in SvelteKit 3. onMount is plain Svelte. {#await import()} also works, but in dev it ran the import on the server, where it failed with no message. One difference to expect: with onMount the server HTML includes the <t-chatbox> tag, which does nothing until the import finishes in the browser.

SvelteKit still has no WebSockets of its own

As of SvelteKit 3.1.1, the feature request has been open since May 2021. A pull request for it closed unmerged in February 2026, and in July a maintainer wrote, “We will revisit WebSockets after” SvelteKit 3. So chat you build yourself on SvelteKit needs a custom server or a hosted service.

TalkJS is that hosted service: the browser connects straight to us over HTTPS and secure WebSockets. Your SvelteKit host serves pages and signs tokens, and no chat traffic goes through it.

The WebSocket connection is the small part of the job. Build chat yourself, even with an AI coding agent, and message history, notifications and moderation are still on you. We store and deliver every message, and email, SMS and mobile push reach people who are offline (SMS is charged separately). Word blacklists and contact masking come on every plan. Development mode is free while you build. After launch you pay per monthly active user, from $279 a month for 10,000 users on Basic, and messages are unlimited.

Where the TalkJS secret key goes in SvelteKit 3

The key goes in your .env file as TALKJS_SECRET_KEY, and a server route reads it through a variable you declare in src/env.ts:

import { defineEnvVars } from '@sveltejs/kit/env';

export const variables = defineEnvVars({
  TALKJS_SECRET_KEY: {},
});

Sign the token in src/routes/api/talkjs-token/+server.ts, after npm install jsonwebtoken. Here locals.user stands for whatever your own login sets:

import jwt from 'jsonwebtoken';
import { json } from '@sveltejs/kit';
import { TALKJS_SECRET_KEY } from '$app/env/private';

export function GET({ locals }) {
  const token = jwt.sign({ tokenType: 'user' }, TALKJS_SECRET_KEY, {
    issuer: '<APP_ID>',
    subject: locals.user.id,
    expiresIn: '1d',
  });
  return json({ token });
}

Then load the token in the chat route’s +page.ts, and pass it to the session with session.setToken(data.token) before the first createIfNotExists:

export const ssr = false;

export async function load({ fetch }) {
  const res = await fetch('/api/talkjs-token');
  return res.json();
}

Here’s one mistake the build does catch: import $app/env/private in a page and npm run build fails with server_only_import. Once the chat loads with a token, turn on authentication in your dashboard, as the authentication docs describe.

Styleseat logo
Because implementing TalkJS was as fast and easy as it was, we were able to greenlight and move this project along very quickly. Had we not chosen this path, the feature would have either been delayed, or shelved due to the resources needed to implement it.
Sean Elliot
Engineering Manager, Styleseat

Ready-made chat UI you can customize

You style the pre-built chat UIs with properties, CSS or a custom theme, as the web components docs show. Need a chat popup? Open a chatbox from a button of your own.

Inbox

The TalkJS Inbox shows the chat history on the left, selected conversation on the right. It’s designed to act as the messaging center of your app, usually living on its own page.

Chatbox

The TalkJS Chatbox is the right half of the Inbox UI mode. It takes up less space and is designed to be embedded in your app near an order confirmation, a booking, or user profile.

Conversation list

The user’s conversations in one list. Put a Chatbox next to it to open the one they pick, or use the Inbox, which combines the two.

FAQs

Pick one that brings the chat UI and the backend together and runs in Svelte as it is. TalkJS does: the chat UI is web components, and Svelte renders those like any other element, so there’s no Svelte adapter package to wait on. You install @talkjs/web-components and @talkjs/core.

TalkJS is built for people talking to people, such as buyers and sellers on a marketplace or a class in an edtech app. A bot is one more participant: it joins 1-on-1 and group chats as a user, and you bring the model. See AI chatbots with TalkJS.

Yes. Nothing renders on a server in a plain Vite app, so the code in the TalkJS Svelte guide ran as written, in dev and in the production build.