简体   繁体   中英

How to use Apollo GraphQL subscriptions in the client-side NextJS?

I am new to NextJS. I have a page that needs to display real-time data pulled from a Hasura GraphQL backend.

In other non-NextJS apps, I have used GraphQL subscriptions with the Apollo client library. Under the hood, this uses websockets.

I can get GraphQL working in NextJS when it's not using subscriptions. I'm pretty sure this is running on the server-side:

import React from "react";
import { AppProps } from "next/app";
import withApollo from 'next-with-apollo';
import { ApolloProvider } from '@apollo/react-hooks';
import ApolloClient, { InMemoryCache } from 'apollo-boost';
import { getToken } from "../util/auth";

interface Props extends AppProps {
    apollo: any
}

const App: React.FC<Props> = ({ Component, pageProps, apollo }) => (
    <ApolloProvider client={apollo}>
        <Component {...pageProps}/>
    </ApolloProvider>
);

export default withApollo(({ initialState }) => new ApolloClient({
    uri: "https://my_hasura_instance.com/v1/graphql",
    cache: new InMemoryCache().restore(initialState || {}),
    request: (operation: any) => {
        const token = getToken();
        operation.setContext({
            headers: {
                authorization: token ? `Bearer ${token}` : ''
            }
        });
    }
}))(App);

And I use it this way:

import { useQuery } from '@apollo/react-hooks';
import { gql } from 'apollo-boost';

const myQuery = gql`
    query {
        ...
    }
`;

const MyComponent: React.FC = () => {
    const { data } = useQuery(myQuery);
    return <p>{JSON.stringify(data)}</p>
}

However, I would instead like to do this:

import { useSubscription } from '@apollo/react-hooks';
import { gql } from 'apollo-boost';


const myQuery = gql`
    subscription {
        ...
    }
`;

const MyComponent: React.FC = () => {
    const { data } = useSubscription(myQuery);
    return <p>{JSON.stringify(data)}</p>
}

What I've tried

I've tried splitting the HttpLink and WebsocketLink elements in the ApolloClient, like so:

import React from "react";
import { AppProps } from "next/app";

import { ApolloProvider } from '@apollo/react-hooks';
import withApollo from 'next-with-apollo';
import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client";
import { split } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';

import { getToken } from "../util/auth";

interface Props extends AppProps {
    apollo: any
}

const App: React.FC<Props> = ({ Component, pageProps, apollo }) => (
    <ApolloProvider client={apollo}>
        <Component {...pageProps}/>
    </ApolloProvider>
);

const wsLink = new WebSocketLink({
    uri: "wss://my_hasura_instance.com/v1/graphql",
    options: {
        reconnect: true,
        timeout: 10000,
        connectionParams: () => ({
            headers: {
                authorization: getToken() ? `Bearer ${getToken()}` : ""
            }
        })
    },
});

const httpLink = new HttpLink({
    uri: "https://hasura-g3uc.onrender.com/v1/graphql",
});

const link = process.browser ? split(
    ({ query }) => {
        const definition = getMainDefinition(query);
        return (
            definition.kind === 'OperationDefinition' &&
            definition.operation === 'subscription'
        );
    },
    wsLink,
    httpLink
) : httpLink;

export default withApollo(({ initialState }) => new ApolloClient({
    link: link,
    cache: new InMemoryCache().restore(initialState || {}),
}))(App);

But when I load the page, I get an Internal Server Error , and this error in the terminal:

Error: Unable to find native implementation, or alternative implementation for WebSocket!

It seems to me that the ApolloClient is then being generated on the server-side, where there is no WebSocket implementation. How can I make this happen on the client-side?

Found workaround to make it work, take look at this answer https://github.com/apollographql/subscriptions-transport-ws/issues/333#issuecomment-359261024

the reason was due to server-side rendering; these statements must run in the browser, so we test if we have process.browser !!

relevant section from the attached github link:

const wsLink = process.browser ? new WebSocketLink({ // if you instantiate in the server, the error will be thrown
  uri: `ws://localhost:4000/subscriptions`,
  options: {
    reconnect: true
  }
}) : null;

const httplink = new HttpLink({
    uri: 'http://localhost:3000/graphql',
    credentials: 'same-origin'
});

const link = process.browser ? split( //only create the split in the browser
  // split based on operation type
  ({ query }) => {
    const { kind, operation } = getMainDefinition(query);
    return kind === 'OperationDefinition' && operation === 'subscription';
  },
  wsLink,
  httplink,
) : httplink;

This answer seems to be more actual

https://github.com/apollographql/subscriptions-transport-ws/issues/333#issuecomment-775578327

You should install ws by npm i ws and add webSocketImpl: ws to WebSocketLink argument.

import ws from 'ws';

const wsLink = new WebSocketLink({
    uri: endpoints.ws,
    options: {
        reconnect: true,
        connectionParams: () => ({
            ...getToken() && {Authorization: getToken()}
        })
    },
    webSocketImpl: ws
});

Solution: Make wsLink a function variable like the code below.

// src/apollo.ts

import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";


const httpLink = new HttpLink({
  uri: 'http://localhost:3000/graphql'
});

const wsLink = () => {
  return new GraphQLWsLink(createClient({
    url: 'ws://localhost:3000/graphql'
  }));
}

export const apolloClient = new ApolloClient({
    link: typeof window === 'undefined' ? httpLink : wsLink(),
    cache: new InMemoryCache(),
  });

// pages/_app.tsx

import { ApolloProvider } from "@apollo/client";
import { apolloClient } from "./apollo";

function MyApp({ Component, pageProps }) {
  return (
    <ApolloProvider client={apolloClient}>
      <Component {...pageProps} />
    </ApolloProvider>
  );
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM