Skip to main content
npayload is launching soon.
npayloadDocs
Framework Quickstarts

Remix quickstart

Add real-time messaging to a Remix application with loaders and actions

Add npayload messaging to a Remix application using loaders to read messages and actions to publish them.

Prerequisites

  • Remix 2+
  • An npayload account with machine credentials (get them here)

Install the SDKs

npm install @npayload/node @npayload/react

Add credentials to .env:

NPAYLOAD_CLIENT_ID=oac_your_client_id
NPAYLOAD_HMAC_SECRET=your_hmac_secret
WEBHOOK_SECRET=your_webhook_secret
NPAYLOAD_PUBLISHABLE_KEY=pk_live_your_publishable_key

Create the server client

// app/lib/npayload.server.ts
import { NPayloadAuth, NPayloadClient } from '@npayload/node';

const auth = new NPayloadAuth({
  clientId: process.env.NPAYLOAD_CLIENT_ID!,
  hmacSecret: process.env.NPAYLOAD_HMAC_SECRET!,
});

export const npayload = new NPayloadClient({ auth });

Add the React provider

// app/root.tsx
import { NPayloadProvider } from '@npayload/react';
import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  useLoaderData,
} from '@remix-run/react';
import type { LoaderFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';

export async function loader({ request }: LoaderFunctionArgs) {
  return json({
    npayloadKey: process.env.NPAYLOAD_PUBLISHABLE_KEY!,
  });
}

export default function App() {
  const { npayloadKey } = useLoaderData<typeof loader>();

  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <NPayloadProvider config={{ publishableKey: npayloadKey }}>
          <Outlet />
        </NPayloadProvider>
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

Build a route with publish and subscribe

// app/routes/messages.tsx
import { json, type ActionFunctionArgs } from '@remix-run/node';
import { useActionData, Form } from '@remix-run/react';
import { useMessages } from '@npayload/react';
import { npayload } from '~/lib/npayload.server';

export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData();
  const text = formData.get('text') as string;

  const message = await npayload.messages.publish({
    channel: 'chat',
    payload: {
      text,
      sentAt: new Date().toISOString(),
    },
  });

  return json({ messageId: message.gid });
}

export default function Messages() {
  const actionData = useActionData<typeof action>();
  const { messages, isLoading } = useMessages('chat');

  return (
    <div>
      <h1>npayload + Remix</h1>

      <Form method="post">
        <input type="text" name="text" placeholder="Type a message" required />
        <button type="submit">Send</button>
      </Form>

      {actionData?.messageId && (
        <p>Published: {actionData.messageId}</p>
      )}

      <h2>Messages</h2>
      {isLoading ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {messages.map((msg) => (
            <li key={msg.gid}>{JSON.stringify(msg.payload)}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

Add a webhook route

// app/routes/webhooks.npayload.ts
import type { ActionFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { npayload } from '~/lib/npayload.server';

export async function action({ request }: ActionFunctionArgs) {
  const body = await request.json();
  const signature = request.headers.get('x-npayload-signature') ?? '';

  const isValid = npayload.webhooks.verify(
    body,
    signature,
    process.env.WEBHOOK_SECRET!
  );

  if (!isValid) {
    return json({ error: 'Invalid signature' }, { status: 401 });
  }

  console.log('Webhook received:', body.payload);
  return json({ received: true });
}

Run it

npm run dev

Open http://localhost:5173/messages and send a message through the form. You can also test via curl:

curl -X POST http://localhost:5173/messages \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "text=Hello+from+curl"

Next steps

Was this page helpful?

On this page