Sergio Xalambrí

Use Remix with

If you want to add real-time capabilities to your Remix app without using an external service, the easiest way is probably with Let's see how to do a quick setup.

tl;dr: See the code in

First we need to create the Remix app, in the install options choose Express

> npx create-remix@latest


💿 Welcome to Remix! Let's get you set up with a new project.

? Where would you like to create your app?
? Where do you want to deploy? Choose Remix if you're unsure, it's easy to chang
e deployment targets. Express Server
? TypeScript or JavaScript? TypeScript
? Do you want me to run `npm install`? Yes

Now, install and

> npm install

Let's go to the server/index.js file created by Remix and add put this:

const path = require("path");
const express = require("express");
const { createServer } = require("http"); // add this require
const { Server } = require(""); // and also require the module
const compression = require("compression");
const morgan = require("morgan");
const { createRequestHandler } = require("@remix-run/express");

const MODE = process.env.NODE_ENV;
const BUILD_DIR = path.join(process.cwd(), "server/build");

const app = express();

// create an httpServer from the Express app
const httpServer = createServer(app);

// and create the server from the httpServer
const io = new Server(httpServer);

// then list to the connection event and get a socket object
io.on("connection", (socket) => {
  // here you can do whatever you want with the socket of the client, in this
  // example I'm logging the of the client
  console.log(, "connected");
  // and I emit an event to the client called `event` with a simple message
  socket.emit("event", "connected!");
  // and I start listening for the event `something`
  socket.on("something", (data) => {
    // log the data together with the who send it
    console.log(, data);
    // and emeit the event again with the message pong
    socket.emit("event", "pong");

app.use(express.static("public", { maxAge: "1h" }));
app.use(express.static("public/build", { immutable: true, maxAge: "1y" }));
  MODE === "production"
    ? createRequestHandler({ build: require("./build") })
    : (req, res, next) => {
        const build = require("./build");
        return createRequestHandler({ build, mode: MODE })(req, res, next);

const port = process.env.PORT || 3000;

// instead of using `app.listen` we use `httpServer.listen`
httpServer.listen(port, () => {
  console.log(`Express server listening on port ${port}`);

function purgeRequireCache() {
  // purge require cache on requests for "server side HMR" this won't let
  // you have in-memory objects between requests in development,
  // alternatively you can set up nodemon/pm2-dev to restart the server on
  // file changes, we prefer the DX of this though, so we've included it
  // for you by default
  for (const key in require.cache) {
    if (key.startsWith(BUILD_DIR)) {
      delete require.cache[key];

That will be all for the WebSocket server, you can now emit or listen for more events as your app needs them.

Let's go to the app code, create a ws.client.ts file somewhere outside routes, and add this:

import io from "";

export function connect() {
  return io("http://localhost:3000");

This function will returnn a new connection to our WebSocket server.

Then, create some context in another file:

import { createContext } from "react";
import { Socket } from "";
import { DefaultEventsMap } from "";

export let wsContext = createContext<
  Socket<DefaultEventsMap, DefaultEventsMap> | undefined

You can also create a custom provider or hook to read it, for this example we will just export it.

Finally, in the root.tsx file add a state and effect to connect to the WebSocket server.

let [socket, setSocket] =
  useState<Socket<DefaultEventsMap, DefaultEventsMap>>();

useEffect(() => {
  let connection = connect();
  return () => {
}, []);

useEffect(() => {
  if (!socket) return;
  socket.on("event", (data) => {
}, [socket]);

And render the context provider wrapping the Outlet.

<wsContext.Provider value={socket}>
  <Outlet />

Now, inside any route you can access this context, get the socket connection object and start emitting or listening to events. For example we could go to routes/index.tsx and add this effect:

let socket = useContext(wsContext);
useEffect(() => {
  if (!socket) return;

  socket.on("event", (data) => {

  socket.emit("something", "ping");
}, [socket]);

You could also emit events inside an event handler, like this:

let socket = useContext(wsContext);

return (
    <button onClick={() => socket.emit("something", "ping")}>Send ping</button>

And that's it!