Skip to content

Conversation

LukeHagar
Copy link
Contributor

@LukeHagar LukeHagar commented Nov 8, 2024

This PR is a replacement to #12961 with a completely different Websocket implementation using crossws that should be fully compatible with all major runtimes.

Functionality has been validated locally using basic tests in the options-2 test app.

Here is the new usage experience.
+server.js

import { error, accept } from '@sveltejs/kit';

export const socket = {
	upgrade(req) {
		 // Accept the websocket connection with a return
		return accept();

                // Reject the websocket connection with an error
                error(401, 'unauthorized');
	},

	open(peer) {
		//... handle socket open
	},

	message(peer, message) {
		//... handle socket message
	},

	close(peer, event) {
		//... handle socket close
	},

	error(peer, error) {
		//... handle socket error
	}
};

The newest implementation allows different sets of handlers to be implemented on a per-route basis. I have tested some basic uses of websockets locally to much success.

This PR is intended to:
Resolve #12358
Resolve #1491

Steps left

  • Ensure handle runs before upgrading requests
  • Gather feedback
  • Add or update tests
  • Fix the types
  • Update the adapters
  • Update the documentation
  • Add a changeset
  • Update language tools +server exports validation
  • Automatic typing for sockets

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Edits

  • Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed.

…ionality for different handlers at different URLs, added example use cases to options-2 test app, added upgrade function for supporting additional adapters, and much more.
Copy link

changeset-bot bot commented Nov 8, 2024

🦋 Changeset detected

Latest commit: 24e8475

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@sveltejs/adapter-cloudflare Minor
@sveltejs/adapter-node Minor
@sveltejs/kit Minor
@sveltejs/adapter-auto Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@LukeHagar LukeHagar mentioned this pull request Nov 8, 2024
6 tasks
@Rich-Harris
Copy link
Member

preview: https://svelte-dev-git-preview-kit-12973-svelte.vercel.app/

this is an automated message

@eltigerchino eltigerchino changed the title Native support for Websockets feat: native support for Websockets Nov 11, 2024
@eltigerchino eltigerchino added the feature / enhancement New feature or request label Nov 11, 2024
@eltigerchino eltigerchino marked this pull request as draft November 11, 2024 03:18
@pi0
Copy link

pi0 commented Jan 22, 2025

@LukeHagar LMK, if you need any help with this (you can reach me also on discord @pi0)

@LukeHagar LukeHagar marked this pull request as ready for review January 23, 2025 14:52
@alanxp
Copy link

alanxp commented Jun 16, 2025

Sorry to reopen the thread again. I was very excited about WebSockets, and even if it’s not as perfect as intended, there should at least be a branch for testing and using crossws in the meantime, while the perfect solution is being worked on.
I was using the temporary commits, and they gave me hope — not just hope, they worked perfectly for me. No downsides whatsoever. They kept me within the Svelte ecosystem.
I really hope WebSockets get the attention they deserve and don’t end up being forgotten.

@Rich-Harris
Copy link
Member

We'll be sharing some thoughts very soon on how we plan to evolve data loading in SvelteKit, and real-time is part of that, but we're mainly thinking about unidirectional stuff (i.e. SSEs). Curious if people have use cases for WebSockets specifically, rather than real-time more broadly?

@ugackMiner53
Copy link

Personally, I use SvelteKit quite a bit with real-time games, where data needs to be sent between the client and server bidirectionally at a fairly constant rate. To my knowledge, using a technique like SSE would require every client to server interaction to create a separate request, adding a lot of overhead for updating variables that need to change quickly, like position. I'm currently using this PR to develop another game, and I've found it to be a very smooth experience when compared to external methods that I've used in the past, which add many complications in server setup and deployment.

(This comment isn't really about the PR, just answering the use-cases question above)

@alanxp
Copy link

alanxp commented Jun 16, 2025

Personally, I use SvelteKit quite a bit with real-time games, where data needs to be sent between the client and server bidirectionally at a fairly constant rate. To my knowledge, using a technique like SSE would require every client to server interaction to create a separate request, adding a lot of overhead for updating variables that need to change quickly, like position. I'm currently using this PR to develop another game, and I've found it to be a very smooth experience when compared to external methods that I've used in the past, which add many complications in server setup and deployment.

(This comment isn't really about the PR, just answering the use-cases question above)

I will use this PR as well. I haven’t had any issues with it so far — I almost finished a game engine using it. I thought it was heading in a good direction.

To answer the question: I use bidirectional communication 99.9% of the time, much more than SSE — not just for games, but also for other projects I’m starting to develop for the company I work for where we need websockets. This was something no other framework offered, and it was truly revolutionary.

@t1u1
Copy link

t1u1 commented Jun 16, 2025

We are using Sveltekit + websockets in two different projects. One is a semi-real time gaming system. The other project is an MVP that needs to handle continuous interaction with thousands of remote systems (which themselves are in production).

We are not using this PR build directly, but I used it as an inspiration to write a custom vite plugin which works mostly fine.

The former project (gaming system) was earlier using SSE (since I hadn't figured out sveltekit+websocket back then). But SSE was a pain to work with. The connection sometimes doesn't close cleanly on the server, eating server resources. And because it's unidirectional, you need to have a separate route for client initiated communication. I don't really see the point of going back to SSE, when a much more capable alternative exists.

@LukeHagar
Copy link
Contributor Author

I would definitely agree that a perfect solution here would allow for native support for SSE, WebSockets, and WebTransport, and the ability to use them independently.

I am greatly excited to hear more about the Svelte team's plans for the direction here, and excited for a sveltey WebSocket experience

@codeit-ninja
Copy link

I actually agree with @Rich-Harris — a lot of developers aren’t even aware that things like Server-Sent Events exist. Most just default to WebSockets and assume that’s the only option for real-time communication. Since SvelteKit is positioning itself as the go-to framework, it really makes sense for it to support all the common real-time patterns — not just WebSockets, but also SSE and others.

@Rich-Harris
Copy link
Member

Here's the RFC for Remote Functions we just published, which hopefully gives you an idea of the broader context.

Essentially, we want to separate data fetching from routing. load functions and actions have a number of awkward quirks and limitations that are solved with remote functions, which, in turn, are made possible by Asynchronous Svelte. Real-time data is no different — there's no real reason it should be tied to a specific route. (If this seems like it contradicts #12973 (comment), that's because the discussion that led to that comment immediately preceded the discussion that lead to the Remote Functions RFC. Both took place at a team offsite in Barcelona immediately before Svelte Summit.)

Not shown in the RFC is this idea for streaming real-time data from the server — query.stream. Quoting from an internal design document:

const sleep = (ms: number) => new Promise((f) => setTimeout(f, ms));

export const time = query.stream(async function* () {
  while (true) {
    yield new Date();
    await sleep(1000);
  }
});

In the client, calling time() would give you the same AsyncIterable, so you could do this...

for await (const t of time()) {
  console.log(`the time is ${t}`);
}

...but time() could also return a promise that resolves to the current value (this sounds weird from a types perspective but it's actually totally fine!):

<script lang="ts">
  import { time } from '$lib/data.remote.ts';
</script>

<p>the time is {await time()}</p>

When new data comes in, the query refreshes itself, causing the expression to be re-evaluated. Because of our global caching for queries read in effects, we don't recreate the query, we just return a promise that resolves to the new value.

Unlike other queries, streams are not batched. This means that when the effect containing await time() is destroyed, we can close the HTTP connection powering the query regardless of which other queries are active — in turn, this causes the stream to end on the server.

This would be powered by Server Sent Events, and would thus be available on every platform that supports streaming responses. It's a good approach because it works the same way everywhere and works very naturally with serverless platforms.

It also has limitations — there's per-request overhead, and it's one-way only. So my question in #12973 (comment) was trying to gauge how many use cases would be satisfied by this design, vs something that is specifically backed by WebSocket, and it sounds like the answer is 'some, but not all'. So the next step would be to figure out how to design something around WebSocket that fits with this broader design direction.

Hope this provides some clarity — I realise that many of you are chomping at the bit for this stuff to be built in!

@samal-rasmussen
Copy link

samal-rasmussen commented Jun 19, 2025

Awesome. This sound like something that is trending in the direction I enjoy.

This would be powered by Server Sent Events, and would thus be available on every platform that supports streaming responses.

This seems relevant:
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API#browser_compatibility
https://caniuse.com/websockets

I work on apps that push data pretty fast, so keeping a websocket open makes sense. I really like doing bidirectional communication over persistent websockets, because opening new connections for every request seems like not the best thing, when you are receiving lots of data regularly anyways.

@LukeHagar
Copy link
Contributor Author

I believe the statement regarding server sent events and platform compatibility is referring to there being hosting platforms that do not support web sockets server side, Vercel being a good example.

@maietta
Copy link

maietta commented Jun 19, 2025

I believe the statement regarding server sent events and platform compatibility is referring to there being hosting platforms that do not support web sockets server side, Vercel being a good example.

I am not sure, but I think it boils down to long running processes are not typical of most hosting options that uses runtime like workerd. If you want them, you need to host it yourself using Bun, Node, Deno, etc. So far, I have not been able to get SSE or WebSockets to work at all with these platforms like Vercel or Cloudflare, but have running my apps via the Bun runtime in an OCI container like Docker with zero issues. Going serverless means restricting what runtime features are present. To leverage long-running process type hosting (not serverless), I typically use Svetle's node adapter even though I am using bun, then pass the --bun flag during runtime like this bun --bun /app/build/index.js.

@nerg4l
Copy link

nerg4l commented Jun 19, 2025

[...] I have not been able to get SSE or WebSockets to work at all with these platforms like Vercel or Cloudflare, [...]

The functionality is available in Cloudflare Workers (link). I personally had no issues when I tried it out, but I did not try it with a complex application.

@weepy
Copy link

weepy commented Jul 1, 2025

I'm building a multiplayer music studio. I use WS for signalling for WebRTC and keeping a general tab on users in rooms etc. I use Socket.io mainly because it provides a bunch of niceties such as room management etc that is actually quite fiddly to get right just using vanillla JS. |

I also use it to keep track in memory of the current "music game state" which is then periodically saved to Mongo based on updates from users and provided to users when they join.

I also use websockets to determine the current "time" - ie User pings GET_TIME and then adds half of the round trip.

Not sure if there's anything there that goes beyond typically "live multiplayer game", but that's above my pay grade ;-)

Currently this is all node as a separate node server which adds a bit of faff and deployment fiddling, though the separation of concerns is a positive.

@humanshield89
Copy link

humanshield89 commented Jul 2, 2025

Here's the RFC for Remote Functions we just published, which hopefully gives you an idea of the broader context.

Essentially, we want to separate data fetching from routing. load functions and actions have a number of awkward quirks and limitations that are solved with remote functions, which, in turn, are made possible by Asynchronous Svelte. Real-time data is no different — there's no real reason it should be tied to a specific route. (If this seems like it contradicts #12973 (comment), that's because the discussion that led to that comment immediately preceded the discussion that lead to the Remote Functions RFC. Both took place at a team offsite in Barcelona immediately before Svelte Summit.)

Not shown in the RFC is this idea for streaming real-time data from the server — query.stream. Quoting from an internal design document:

const sleep = (ms: number) => new Promise((f) => setTimeout(f, ms));

export const time = query.stream(async function* () {
  while (true) {
    yield new Date();
    await sleep(1000);
  }
});

In the client, calling time() would give you the same AsyncIterable, so you could do this...

for await (const t of time()) {
  console.log(`the time is ${t}`);
}

...but time() could also return a promise that resolves to the current value (this sounds weird from a types perspective but it's actually totally fine!):

<script lang="ts">
  import { time } from '$lib/data.remote.ts';
</script>

<p>the time is {await time()}</p>

When new data comes in, the query refreshes itself, causing the expression to be re-evaluated. Because of our global caching for queries read in effects, we don't recreate the query, we just return a promise that resolves to the new value.
Unlike other queries, streams are not batched. This means that when the effect containing await time() is destroyed, we can close the HTTP connection powering the query regardless of which other queries are active — in turn, this causes the stream to end on the server.

This would be powered by Server Sent Events, and would thus be available on every platform that supports streaming responses. It's a good approach because it works the same way everywhere and works very naturally with serverless platforms.

It also has limitations — there's per-request overhead, and it's one-way only. So my question in #12973 (comment) was trying to gauge how many use cases would be satisfied by this design, vs something that is specifically backed by WebSocket, and it sounds like the answer is 'some, but not all'. So the next step would be to figure out how to design something around WebSocket that fits with this broader design direction.

Hope this provides some clarity — I realise that many of you are chomping at the bit for this stuff to be built in!

As someone who has been using SSE with Sveltekit for a couple years now, this sounds great. I have yet to face a problem that required me to use WebSocket.

I have a question will every query.stream have it's own endpoint and connection or will sveltekit group all of them under the hood ? I am assuming it's the managed by sveltekit.

Since if each stream uses it's own endpoint and connection, having multiple real time streams is inefficient, especially on servers.

@eltigerchino
Copy link
Member

I have a question will every query.stream have it's own endpoint and connection or will sveltekit group all of them under the hood ? I am assuming it's the managed by sveltekit.

Since if each stream uses it's own endpoint and connection, having multiple real time streams is inefficient, especially on servers.

Yes, the plan is to re-use the same connection for multiple query.stream calls. In contrast, this current PR does not do that, so it is inefficient.

@philholden
Copy link

Websockets allow binary data SSE does not. Automerge uses binary data to sync efficent conflict free replication data structures (Google docs style multi user experiences). Svelte's reactive nature makes it a good fit for these kinds of realtime multi user apps.

@NickClark
Copy link

This is looking great!

Please make sure there's a way to catch client disconnects on the server. In ORTC we are given an AbortSignal that we can use to cancel/cleanup the server side resources if the SSE connection is dropped.

export const time = query.stream(async function* ({signal}) {
  signal.addEventListener("abort", () => { /* ...cleanup... */ })
  //...
});

This also very useful for all the other libraries that use the same pattern, you can just pass this along instead of making your own AbortController.

@atomtables
Copy link

atomtables commented Jul 10, 2025

This looks really nice!

Is there a method to get URL slugs from a peer connection? Right now the only way to get the slug is during the upgrade connection and by manually parsing the peer.request.url string (as far as I know).

@gnanakeethan
Copy link

This is really great change coming into SvelteKit. I am wondering what prevents it from being merged into main-stream except for the single step that says 'Update language tools +server exports validation' .

I have been a long term user of Svelte. I hope I can contribute if there is anything that can be done.

@eltigerchino
Copy link
Member

eltigerchino commented Jul 28, 2025

I am wondering what prevents it from being merged into main-stream except for the single step that says 'Update language tools +server exports validation' .

#12973 (comment)

@alanxp
Copy link

alanxp commented Jul 28, 2025

This is really great change coming into SvelteKit. I am wondering what prevents it from being merged into main-stream except for the single step that says 'Update language tools +server exports validation' .

I have been a long term user of Svelte. I hope I can contribute if there is anything that can be done.

I know your feeling bro.

I havent been programming in a while, i'm also waiting for this to become merge into the main-stream, i dont know why they keep delaying such an important feature. AI is winning the race :(

@einarpersson
Copy link

This is the most wanted new feature for me when it comes to sveltekit. As a adapter-node user I was a bit worried that something like this would not be pursued as it didn't fit with some serverless adapters (eg. vercel) but I'm glad to be proven wrong. This together with the new remote functions capability feels great.

Personally I would mainly use it for unidirectional (push) events at the moment, but I suppose having support for both SSE and websockets would be nice.

Really hope this is high on the "coming next" list.

@AgarwalPragy
Copy link

AgarwalPragy commented Aug 29, 2025

It's crazy to think that remote functions just dropped, and now all I want is remote functions and query.stream over websockets! It will drastically simplify my app, to a point that it feels like cheating.

My usecase is a conversational AI app, and I need to continuously send data both ways.

@Rich-Harris Thank you for all these amazing features! Eagerly waiting for websockets <3

@benmccann
Copy link
Member

This PR works for me where query.stream does not because I want to communicate to an external client (a robot) that is not a SvelteKit web page.

@LukeHagar
Copy link
Contributor Author

This PR works for me where query.stream does not because I want to communicate to an external client (a robot) that is not a SvelteKit web page.

I'll second this, I have many uses for the native Websocket protocol, which are not usable when the endpoints and connections are all abstracted away

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature / enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

implement "Upgrade" as a function in a api route to support websocket Native support for web sockets