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/reactAdd 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_keyCreate 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 devOpen 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
- Next.js quickstart for a Next.js comparison
- React SDK for all available hooks
- Webhooks guide for production webhook patterns
Was this page helpful?