Revere CRE is hiring! Interested in working on the cutting edge of frontend?
Reach out to eng-jobs@reverecre.com for more information.
Relay + Next.js

Documentation |
Discussion |
Latest Releases
relay-nextjs
is the best way to use Relay and Next.js in the same project! It supports
incremental migration, is suspense ready, and is run in production by major
companies.
Overview
relay-nextjs
wraps page components, a GraphQL query, and some helper methods
to automatically hook up data fetching using Relay. On initial load a Relay
environment is created, the data is fetched server-side, the page is rendered,
and resulting state is serialized as a script tag. On boot in the client a new
Relay environment and preloaded query are created using that serialized state.
Data is fetched using the client-side Relay environment on subsequent
navigations.
Note: relay-nextjs
does not support Nextjs 13 App Router at the moment.
Getting Started
Install using npm or your other favorite package manager:
$ npm install relay-nextjs
relay-nextjs
must be configured in _app
to properly intercept and handle
routing.
Setting up the Relay Environment
For basic information about the Relay environment please see the
Relay docs.
relay-nextjs
was designed with both client-side and server-side rendering in
mind. As such it needs to be able to use either a client-side or server-side
Relay environment. The library knows how to handle which environment to use, but
we have to tell it how to create these environments. For this we will define two
functions: getClientEnvironment
and createServerEnvironment
. Note the
distinction β on the client only one environment is ever created because there
is only one app, but on the server we must create an environment per-render to
ensure the cache is not shared between requests.
First we'll define getClientEnvironment
:
import { Environment, Network, Store, RecordSource } from 'relay-runtime';
export function createClientNetwork() {
return Network.create(async (params, variables) => {
const response = await fetch('/api/graphql', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: params.text,
variables,
}),
});
const json = await response.text();
return JSON.parse(json);
});
}
let clientEnv: Environment | undefined;
export function getClientEnvironment() {
if (typeof window === 'undefined') return null;
if (clientEnv == null) {
clientEnv = new Environment({
network: createClientNetwork(),
store: new Store(new RecordSource()),
isServer: false,
});
}
return clientEnv;
}
and then createServerEnvironment
:
import { graphql } from 'graphql';
import { GraphQLResponse, Network } from 'relay-runtime';
import { schema } from 'lib/schema';
export function createServerNetwork() {
return Network.create(async (text, variables) => {
const context = {
token,
};
const results = await graphql({
schema,
source: text.text!,
variableValues: variables,
contextValue: context,
});
return JSON.parse(JSON.stringify(results)) as GraphQLResponse;
});
}
export function createServerEnvironment() {
return new Environment({
network: createServerNetwork(),
store: new Store(new RecordSource()),
isServer: true,
});
}
Note in the example server environment weβre executing against a local schema
but you may fetch from a remote API as well.
Configuring _app
import { RelayEnvironmentProvider } from 'react-relay/hooks';
import { useRelayNextjs } from 'relay-nextjs/app';
import { getClientEnvironment } from '../lib/client_environment';
function MyApp({ Component, pageProps }: AppProps) {
const { env, ...relayProps } = useRelayNextjs(pageProps, {
createClientEnvironment: () => getClientSideEnvironment()!,
});
return (
<>
<RelayEnvironmentProvider environment={env}>
<Component {...pageProps} {...relayProps} />
</RelayEnvironmentProvider>
</>
);
}
export default MyApp;
Usage in a Page
import { withRelay, RelayProps } from 'relay-nextjs';
import { graphql, usePreloadedQuery } from 'react-relay/hooks';
const ProfileQuery = graphql`
query profile_ProfileQuery($uuid: ID!) {
user(id: $uuid) {
id
firstName
lastName
}
}
`;
function UserProfile({ preloadedQuery }: RelayProps<{}, profile_ProfileQuery>) {
const query = usePreloadedQuery(ProfileQuery, preloadedQuery);
return (
<div>
Hello {query.user.firstName} {query.user.lastName}
</div>
);
}
function Loading() {
return <div>Loading...</div>;
}
export default withRelay(UserProfile, UserProfileQuery, {
fallback: <Loading />,
createClientEnvironment: () => getClientEnvironment()!,
serverSideProps: async (ctx) => {
const { getTokenFromCtx } = await import('lib/server/auth');
const token = await getTokenFromCtx(ctx);
if (token == null) {
return {
redirect: { destination: '/login', permanent: false },
};
}
return { token };
},
createServerEnvironment: async (
ctx,
{ token }: { token: string }
) => {
const { createServerEnvironment } = await import('lib/server_environment');
return createServerEnvironment(token);
},
});