Apollo Client recipes
Share GraphQL subscriptions through Apollo's client.
GraphQL over WebSocket · Next.js
Before you start
Section titled “Before you start”Start with an existing Next.js app. Keep its renderer, routes and plugins. These files add one live view; Spinetab does not create your API.
Your schema exposes Subscription.queue: Queue! with Queue.open: Int!. Each event contains the complete queue value. Serve the graphql-transport-ws protocol at /graphql. Queries and mutations, if used, go to /graphql. This is a GraphQL endpoint, not a plain SSE or WebSocket feed.
The examples use same-origin URLs and declare
anonymous: true: no token is supplied, although cookies
still flow. For a separate API origin, use its URL and configure CORS or
your application's proxy. For private data, add
credentials and user scopes;
never put a secret in these files.
1. Install
Section titled “1. Install”pnpm add spinetab graphql@^17.0.2 graphql-ws@^6.3.0 @apollo/client@^4.3.1 rxjs@^7.8.2 @apollo/client-integration-nextjs@^0.14.5Keep your framework's existing dependencies. See compatible versions if upgrading an older app.
2. Configure your app
Section titled “2. Configure your app”Merge this addition into your configuration; preserve existing plugins and options. Restart the dev server afterwards. The plugin bundles the worker and discovers the adapters imported below.
import type { NextConfig } from "next";import { withSpinetab } from "spinetab/next";
const config: NextConfig = {};export default withSpinetab(config);3. Connect and render
Section titled “3. Connect and render”Save these files together in app/recipe/.
The provider supplies Apollo's client to its subscription hook. ApolloNextAppProvider creates the client for the Next.js render context. This view subscribes only in the browser; use an absolute HTTP URI and your request authentication if adding server-rendered queries.
reconcile: "latest" fits this full-state contract: the next complete event restores the displayed value after a gap. Use a refresh/merge policy for deltas or partial GraphQL results. Reconnecting alone does not reconstruct missed state.
import { createSpinetab } from "spinetab";
export const spinetab = createSpinetab({ anonymous: true });export type Queue = { open: number };export type QueueView = { open?: number; problem?: string };import { graphqlWs } from "spinetab/graphql-ws";
export const endpoint = graphqlWs("/graphql");import { gql, type TypedDocumentNode } from "@apollo/client";import type { Queue } from "./queue-types";
export const QUEUE: TypedDocumentNode<{ queue: Queue }> = gql` subscription Queue { queue { open } }`;import { useSubscription } from "@apollo/client/react";import { QUEUE } from "./document";
export default function Queue() { const { data, error } = useSubscription(QUEUE); if (error) return <p role="alert">{error.message}</p>; if (!data) return <p role="status">Loading queue…</p>; return ( <p> <output>{data.queue.open}</output> open </p> );}"use client";
import { HttpLink } from "@apollo/client";import { ApolloClient, ApolloNextAppProvider, InMemoryCache,} from "@apollo/client-integration-nextjs";import { spinetabSplit } from "spinetab/apollo";import { endpoint } from "./endpoint";import { spinetab } from "./live";import Queue from "./Queue";
function makeClient() { return new ApolloClient({ cache: new InMemoryCache(), link: spinetabSplit(spinetab, endpoint, new HttpLink({ uri: "/graphql" }), { reconcile: "latest", }), });}
export default function Recipe() { return ( <ApolloNextAppProvider makeClient={makeClient}> <Queue /> </ApolloNextAppProvider> );}4. Mount the view
Section titled “4. Mount the view”"use client";
import Recipe from "./recipe/Recipe";export default function Live() { return <Recipe />;}import Live from "./Live";export default function Page() { return ( <main> <Live /> </main> );}The "use client" boundary includes the view and its imports. Keep subscriptions out of Server Components, loaders and server actions. Retain your existing root layout; use src/app/ instead if that is your app directory. Both supported Next bundlers use this configuration.
Check the result
Section titled “Check the result”Open the page in two tabs of the same browser profile. Both should show advancing queue values. Matching subscriptions share upstream work when SharedWorker is available; fallback runs independently in each tab. Remove one view and the other should keep updating. Removing the last view releases its subscription; connection closure can follow the adapter's idle delay.