Migrate from Pusher to Vask (Next.js). Same SDK, New Host.

Vask speaks the Pusher protocol, so a Next.js app using pusher-js and the pusher server package usually migrates by changing environment values and the explicit host.

Migration summary

At a glance

low risk
Type
Drop-in host swap
Typical time
10-15 minutes
Server SDK
pusher npm package unchanged
Client SDK
pusher-js unchanged
Auth changes
reuse existing API route; keep server secret private
Rollback
restore Pusher env values and redeploy

Files touched

  • .env.local
  • lib/pusher-client.ts
  • lib/pusher-server.ts
  • app/api or pages/api auth route

Get started

Realtime made simple.

Free Tier: 500K broadcasts/mo and 100 concurrent across unlimited apps. $10/mo when you outgrow it.

Minimal config diff

Set the Vask app key as both PUSHER_APP_ID and PUSHER_APP_KEY. If your SDK asks for an app id, use the Vask app key. Vask does not issue a separate customer-facing app id.

- PUSHER_APP_ID=123456
- PUSHER_APP_KEY=abc123
- PUSHER_APP_SECRET=xyz789
- PUSHER_APP_CLUSTER=us3
- NEXT_PUBLIC_PUSHER_APP_KEY=abc123
+ PUSHER_APP_ID=your_vask_key
+ PUSHER_APP_KEY=your_vask_key
+ PUSHER_APP_SECRET=your_vask_secret
+ PUSHER_HOST=wss.vask.dev
+ PUSHER_APP_CLUSTER=mt1
+ NEXT_PUBLIC_PUSHER_APP_KEY=your_vask_key
+ NEXT_PUBLIC_PUSHER_HOST=wss.vask.dev
+ NEXT_PUBLIC_PUSHER_APP_CLUSTER=mt1

NEXT_PUBLIC_* values ship to the browser. Never expose PUSHER_APP_SECRET through NEXT_PUBLIC_*, next.config.js, or rendered props.

Rollback

Restore the old Pusher environment values, remove or override PUSHER_HOST, redeploy the Next.js app, and hard refresh a browser session. No SDK downgrade is required.

What changes / what stays

  • Changes: host, key, secret, public client host, and any shared server Pusher instance.
  • Stays: pusher-js, channel.bind, trigger, channel names, event names, auth endpoint shape, and presence callbacks.

Client subscribe

Use this in a Client Component or a browser-only shared module:

import Pusher from 'pusher-js';

export const pusherClient = new Pusher(
    process.env.NEXT_PUBLIC_PUSHER_APP_KEY!,
    {
        cluster: process.env.NEXT_PUBLIC_PUSHER_APP_CLUSTER || 'mt1',
        wsHost: process.env.NEXT_PUBLIC_PUSHER_HOST || 'wss.vask.dev',
        wsPort: 443,
        wssPort: 443,
        forceTLS: true,
        enabledTransports: ['ws', 'wss'],
        authEndpoint: '/api/pusher/auth',
    },
);

For App Router, import this only from files marked 'use client' or from modules used exclusively by Client Components. For Pages Router, the same module can be imported from page components.

Server publish

Use one server-side Pusher instance for Route Handlers, API Routes, jobs, and auth:

import Pusher from 'pusher';

export const pusherServer = new Pusher({
    appId: process.env.PUSHER_APP_ID!,
    key: process.env.PUSHER_APP_KEY!,
    secret: process.env.PUSHER_APP_SECRET!,
    cluster: process.env.PUSHER_APP_CLUSTER || 'mt1',
    host: process.env.PUSHER_HOST || 'wss.vask.dev',
    port: '443',
    useTLS: true,
});

Pages Router uses this from pages/api/*.ts. App Router uses it from app/api/**/route.ts.

Private and presence channels

The auth endpoint keeps the same signature:

// app/api/pusher/auth/route.ts
import { NextResponse } from 'next/server';
import { pusherServer } from '@/lib/pusher-server';

export async function POST(request: Request) {
    const formData = await request.formData();
    const socketId = String(formData.get('socket_id'));
    const channelName = String(formData.get('channel_name'));

    return NextResponse.json(
        pusherServer.authorizeChannel(socketId, channelName),
    );
}

For Pages Router, keep the same authorizeChannel(req.body.socket_id, req.body.channel_name) call inside the API Route.

Verify

  1. Deploy or restart Next.js with the new environment.
  2. Hard refresh and confirm the browser opens a WebSocket to wss.vask.dev.
  3. Trigger from a Pages API Route or App Router Route Handler.
  4. Test one public channel and one private or presence channel.

Gotchas

  • Edge Runtime. If a Route Handler exports runtime = 'edge', avoid the pusher npm package there. Use a Node.js route or the REST API with fetch.
  • App Router imports. Do not import the browser Pusher client into Server Components. Keep client and server Pusher modules separate.
  • Public vs server env. The key and host can be public; the secret cannot. Audit NEXT_PUBLIC_* and any config exposed through next.config.js.
  • Multiple instances. Grep for new Pusher( and update every browser and server instance, not just the shared module you expect to be used.

Where to go next

Do App Router and Pages Router both work?
Yes. Client subscriptions use the same pusher-js config. Server triggers live in Route Handlers for App Router projects and API Routes for Pages Router projects.
Can I trigger from the Edge Runtime?
Usually no with the pusher npm package, because it expects Node APIs. Move the trigger to a Node.js Route Handler or call the Vask REST API with fetch from the edge.
Which environment variables are public?
Only expose NEXT_PUBLIC_PUSHER_APP_KEY, NEXT_PUBLIC_PUSHER_HOST, and an optional NEXT_PUBLIC_PUSHER_APP_CLUSTER placeholder. PUSHER_APP_SECRET stays server-only.
Do private and presence channels need new auth logic?
No. Keep your existing auth endpoint and authorizeChannel call. Update the shared server Pusher instance so the endpoint signs with the Vask secret.

Get going

Vask keeps the Next.js migration at the config layer: same browser SDK, same server package, same auth endpoint, new host.

Make the switch

Same SDK. New host. Done.

Point your pusher-js client at Vask, keep your existing channel subscriptions and event handlers. Most teams ship the cutover in under fifteen minutes.