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](https://nextjs.org/docs/app) at the moment.
- See [GitHub issue #89](https://github.com/RevereCRE/relay-nextjs/issues/89) for more info.
## Getting Started
Install using npm or your other favorite package manager:
```sh
$ 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](https://relay.dev/docs/getting-started/step-by-step-guide/#42-configure-relay-runtime).
`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`:
```tsx
// lib/client_environment.ts
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`:
```tsx
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,
// More context variables here
};
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`
```tsx
// pages/_app.tsx
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 (
<>
>
);
}
export default MyApp;
```
## Usage in a Page
```tsx
// src/pages/user/[uuid].tsx
import { withRelay, RelayProps } from 'relay-nextjs';
import { graphql, usePreloadedQuery } from 'react-relay/hooks';
// The $uuid variable is injected automatically from the route.
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 (
;
}
export default withRelay(UserProfile, UserProfileQuery, {
// Fallback to render while the page is loading.
// This property is optional.
fallback: ,
// Create a Relay environment on the client-side.
// Note: This function must always return the same value.
createClientEnvironment: () => getClientEnvironment()!,
// variablesFromContext allows you to declare and customize variables for the graphql query.
// by default variablesFromContext is ctx.query
variablesFromContext: (ctx: NextRouter | NextPageContext) => ({ ...ctx.query, otherVariable: true }),
// Gets server side props for the page.
serverSideProps: async (ctx) => {
// This is an example of getting an auth token from the request context.
// If you don't need to authenticate users this can be removed and return an
// empty object instead.
const { getTokenFromCtx } = await import('lib/server/auth');
const token = await getTokenFromCtx(ctx);
if (token == null) {
return {
redirect: { destination: '/login', permanent: false },
};
}
return { token };
},
// Server-side props can be accessed as the second argument
// to this function.
createServerEnvironment: async (
ctx,
// The object returned from serverSideProps. If you don't need a token
// you can remove this argument.
{ token }: { token: string }
) => {
const { createServerEnvironment } = await import('lib/server_environment');
return createServerEnvironment(token);
},
});
```
================================================
FILE: example/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
================================================
FILE: example/.prettierignore
================================================
.next
data
node_modules
**/__generated__
public/
================================================
FILE: example/.prettierrc
================================================
{
"singleQuote": true,
"bracketSpacing": true,
"arrowParens": "always"
}
================================================
FILE: example/README.md
================================================
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
================================================
FILE: example/data/schema.graphql
================================================
schema {
query: Root
}
"""A single film."""
type Film implements Node {
"""The title of this film."""
title: String
"""The episode number of this film."""
episodeID: Int
"""The opening paragraphs at the beginning of this film."""
openingCrawl: String
"""The name of the director of this film."""
director: String
"""The name(s) of the producer(s) of this film."""
producers: [String]
"""The ISO 8601 date format of film release at original creator country."""
releaseDate: String
speciesConnection(after: String, first: Int, before: String, last: Int): FilmSpeciesConnection
starshipConnection(after: String, first: Int, before: String, last: Int): FilmStarshipsConnection
vehicleConnection(after: String, first: Int, before: String, last: Int): FilmVehiclesConnection
characterConnection(after: String, first: Int, before: String, last: Int): FilmCharactersConnection
planetConnection(after: String, first: Int, before: String, last: Int): FilmPlanetsConnection
"""The ISO 8601 date format of the time that this resource was created."""
created: String
"""The ISO 8601 date format of the time that this resource was edited."""
edited: String
"""The ID of an object"""
id: ID!
}
"""A connection to a list of items."""
type FilmCharactersConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [FilmCharactersEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
characters: [Person]
}
"""An edge in a connection."""
type FilmCharactersEdge {
"""The item at the end of the edge"""
node: Person
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type FilmPlanetsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [FilmPlanetsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
planets: [Planet]
}
"""An edge in a connection."""
type FilmPlanetsEdge {
"""The item at the end of the edge"""
node: Planet
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type FilmsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [FilmsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
films: [Film]
}
"""An edge in a connection."""
type FilmsEdge {
"""The item at the end of the edge"""
node: Film
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type FilmSpeciesConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [FilmSpeciesEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
species: [Species]
}
"""An edge in a connection."""
type FilmSpeciesEdge {
"""The item at the end of the edge"""
node: Species
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type FilmStarshipsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [FilmStarshipsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
starships: [Starship]
}
"""An edge in a connection."""
type FilmStarshipsEdge {
"""The item at the end of the edge"""
node: Starship
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type FilmVehiclesConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [FilmVehiclesEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
vehicles: [Vehicle]
}
"""An edge in a connection."""
type FilmVehiclesEdge {
"""The item at the end of the edge"""
node: Vehicle
"""A cursor for use in pagination"""
cursor: String!
}
"""An object with an ID"""
interface Node {
"""The id of the object."""
id: ID!
}
"""Information about pagination in a connection."""
type PageInfo {
"""When paginating forwards, are there more items?"""
hasNextPage: Boolean!
"""When paginating backwards, are there more items?"""
hasPreviousPage: Boolean!
"""When paginating backwards, the cursor to continue."""
startCursor: String
"""When paginating forwards, the cursor to continue."""
endCursor: String
}
"""A connection to a list of items."""
type PeopleConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [PeopleEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
people: [Person]
}
"""An edge in a connection."""
type PeopleEdge {
"""The item at the end of the edge"""
node: Person
"""A cursor for use in pagination"""
cursor: String!
}
"""An individual person or character within the Star Wars universe."""
type Person implements Node {
"""The name of this person."""
name: String
"""
The birth year of the person, using the in-universe standard of BBY or ABY -
Before the Battle of Yavin or After the Battle of Yavin. The Battle of Yavin is
a battle that occurs at the end of Star Wars episode IV: A New Hope.
"""
birthYear: String
"""
The eye color of this person. Will be "unknown" if not known or "n/a" if the
person does not have an eye.
"""
eyeColor: String
"""
The gender of this person. Either "Male", "Female" or "unknown",
"n/a" if the person does not have a gender.
"""
gender: String
"""
The hair color of this person. Will be "unknown" if not known or "n/a" if the
person does not have hair.
"""
hairColor: String
"""The height of the person in centimeters."""
height: Int
"""The mass of the person in kilograms."""
mass: Float
"""The skin color of this person."""
skinColor: String
"""A planet that this person was born on or inhabits."""
homeworld: Planet
filmConnection(after: String, first: Int, before: String, last: Int): PersonFilmsConnection
"""The species that this person belongs to, or null if unknown."""
species: Species
starshipConnection(after: String, first: Int, before: String, last: Int): PersonStarshipsConnection
vehicleConnection(after: String, first: Int, before: String, last: Int): PersonVehiclesConnection
"""The ISO 8601 date format of the time that this resource was created."""
created: String
"""The ISO 8601 date format of the time that this resource was edited."""
edited: String
"""The ID of an object"""
id: ID!
}
"""A connection to a list of items."""
type PersonFilmsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [PersonFilmsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
films: [Film]
}
"""An edge in a connection."""
type PersonFilmsEdge {
"""The item at the end of the edge"""
node: Film
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type PersonStarshipsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [PersonStarshipsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
starships: [Starship]
}
"""An edge in a connection."""
type PersonStarshipsEdge {
"""The item at the end of the edge"""
node: Starship
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type PersonVehiclesConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [PersonVehiclesEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
vehicles: [Vehicle]
}
"""An edge in a connection."""
type PersonVehiclesEdge {
"""The item at the end of the edge"""
node: Vehicle
"""A cursor for use in pagination"""
cursor: String!
}
"""
A large mass, planet or planetoid in the Star Wars Universe, at the time of
0 ABY.
"""
type Planet implements Node {
"""The name of this planet."""
name: String
"""The diameter of this planet in kilometers."""
diameter: Int
"""
The number of standard hours it takes for this planet to complete a single
rotation on its axis.
"""
rotationPeriod: Int
"""
The number of standard days it takes for this planet to complete a single orbit
of its local star.
"""
orbitalPeriod: Int
"""
A number denoting the gravity of this planet, where "1" is normal or 1 standard
G. "2" is twice or 2 standard Gs. "0.5" is half or 0.5 standard Gs.
"""
gravity: String
"""The average population of sentient beings inhabiting this planet."""
population: Float
"""The climates of this planet."""
climates: [String]
"""The terrains of this planet."""
terrains: [String]
"""
The percentage of the planet surface that is naturally occuring water or bodies
of water.
"""
surfaceWater: Float
residentConnection(after: String, first: Int, before: String, last: Int): PlanetResidentsConnection
filmConnection(after: String, first: Int, before: String, last: Int): PlanetFilmsConnection
"""The ISO 8601 date format of the time that this resource was created."""
created: String
"""The ISO 8601 date format of the time that this resource was edited."""
edited: String
"""The ID of an object"""
id: ID!
}
"""A connection to a list of items."""
type PlanetFilmsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [PlanetFilmsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
films: [Film]
}
"""An edge in a connection."""
type PlanetFilmsEdge {
"""The item at the end of the edge"""
node: Film
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type PlanetResidentsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [PlanetResidentsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
residents: [Person]
}
"""An edge in a connection."""
type PlanetResidentsEdge {
"""The item at the end of the edge"""
node: Person
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type PlanetsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [PlanetsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
planets: [Planet]
}
"""An edge in a connection."""
type PlanetsEdge {
"""The item at the end of the edge"""
node: Planet
"""A cursor for use in pagination"""
cursor: String!
}
type Root {
allFilms(after: String, first: Int, before: String, last: Int): FilmsConnection
film(id: ID, filmID: ID): Film
allPeople(after: String, first: Int, before: String, last: Int): PeopleConnection
person(id: ID, personID: ID): Person
allPlanets(after: String, first: Int, before: String, last: Int): PlanetsConnection
planet(id: ID, planetID: ID): Planet
allSpecies(after: String, first: Int, before: String, last: Int): SpeciesConnection
species(id: ID, speciesID: ID): Species
allStarships(after: String, first: Int, before: String, last: Int): StarshipsConnection
starship(id: ID, starshipID: ID): Starship
allVehicles(after: String, first: Int, before: String, last: Int): VehiclesConnection
vehicle(id: ID, vehicleID: ID): Vehicle
"""Fetches an object given its ID"""
node(
"""The ID of an object"""
id: ID!
): Node
}
"""A type of person or character within the Star Wars Universe."""
type Species implements Node {
"""The name of this species."""
name: String
"""The classification of this species, such as "mammal" or "reptile"."""
classification: String
"""The designation of this species, such as "sentient"."""
designation: String
"""The average height of this species in centimeters."""
averageHeight: Float
"""The average lifespan of this species in years, null if unknown."""
averageLifespan: Int
"""
Common eye colors for this species, null if this species does not typically
have eyes.
"""
eyeColors: [String]
"""
Common hair colors for this species, null if this species does not typically
have hair.
"""
hairColors: [String]
"""
Common skin colors for this species, null if this species does not typically
have skin.
"""
skinColors: [String]
"""The language commonly spoken by this species."""
language: String
"""A planet that this species originates from."""
homeworld: Planet
personConnection(after: String, first: Int, before: String, last: Int): SpeciesPeopleConnection
filmConnection(after: String, first: Int, before: String, last: Int): SpeciesFilmsConnection
"""The ISO 8601 date format of the time that this resource was created."""
created: String
"""The ISO 8601 date format of the time that this resource was edited."""
edited: String
"""The ID of an object"""
id: ID!
}
"""A connection to a list of items."""
type SpeciesConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [SpeciesEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
species: [Species]
}
"""An edge in a connection."""
type SpeciesEdge {
"""The item at the end of the edge"""
node: Species
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type SpeciesFilmsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [SpeciesFilmsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
films: [Film]
}
"""An edge in a connection."""
type SpeciesFilmsEdge {
"""The item at the end of the edge"""
node: Film
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type SpeciesPeopleConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [SpeciesPeopleEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
people: [Person]
}
"""An edge in a connection."""
type SpeciesPeopleEdge {
"""The item at the end of the edge"""
node: Person
"""A cursor for use in pagination"""
cursor: String!
}
"""A single transport craft that has hyperdrive capability."""
type Starship implements Node {
"""The name of this starship. The common name, such as "Death Star"."""
name: String
"""
The model or official name of this starship. Such as "T-65 X-wing" or "DS-1
Orbital Battle Station".
"""
model: String
"""
The class of this starship, such as "Starfighter" or "Deep Space Mobile
Battlestation"
"""
starshipClass: String
"""The manufacturers of this starship."""
manufacturers: [String]
"""The cost of this starship new, in galactic credits."""
costInCredits: Float
"""The length of this starship in meters."""
length: Float
"""The number of personnel needed to run or pilot this starship."""
crew: String
"""The number of non-essential people this starship can transport."""
passengers: String
"""
The maximum speed of this starship in atmosphere. null if this starship is
incapable of atmosphering flight.
"""
maxAtmospheringSpeed: Int
"""The class of this starships hyperdrive."""
hyperdriveRating: Float
"""
The Maximum number of Megalights this starship can travel in a standard hour.
A "Megalight" is a standard unit of distance and has never been defined before
within the Star Wars universe. This figure is only really useful for measuring
the difference in speed of starships. We can assume it is similar to AU, the
distance between our Sun (Sol) and Earth.
"""
MGLT: Int
"""The maximum number of kilograms that this starship can transport."""
cargoCapacity: Float
"""
The maximum length of time that this starship can provide consumables for its
entire crew without having to resupply.
"""
consumables: String
pilotConnection(after: String, first: Int, before: String, last: Int): StarshipPilotsConnection
filmConnection(after: String, first: Int, before: String, last: Int): StarshipFilmsConnection
"""The ISO 8601 date format of the time that this resource was created."""
created: String
"""The ISO 8601 date format of the time that this resource was edited."""
edited: String
"""The ID of an object"""
id: ID!
}
"""A connection to a list of items."""
type StarshipFilmsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [StarshipFilmsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
films: [Film]
}
"""An edge in a connection."""
type StarshipFilmsEdge {
"""The item at the end of the edge"""
node: Film
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type StarshipPilotsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [StarshipPilotsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
pilots: [Person]
}
"""An edge in a connection."""
type StarshipPilotsEdge {
"""The item at the end of the edge"""
node: Person
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type StarshipsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [StarshipsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
starships: [Starship]
}
"""An edge in a connection."""
type StarshipsEdge {
"""The item at the end of the edge"""
node: Starship
"""A cursor for use in pagination"""
cursor: String!
}
"""A single transport craft that does not have hyperdrive capability"""
type Vehicle implements Node {
"""
The name of this vehicle. The common name, such as "Sand Crawler" or "Speeder
bike".
"""
name: String
"""
The model or official name of this vehicle. Such as "All-Terrain Attack
Transport".
"""
model: String
"""The class of this vehicle, such as "Wheeled" or "Repulsorcraft"."""
vehicleClass: String
"""The manufacturers of this vehicle."""
manufacturers: [String]
"""The cost of this vehicle new, in Galactic Credits."""
costInCredits: Float
"""The length of this vehicle in meters."""
length: Float
"""The number of personnel needed to run or pilot this vehicle."""
crew: String
"""The number of non-essential people this vehicle can transport."""
passengers: String
"""The maximum speed of this vehicle in atmosphere."""
maxAtmospheringSpeed: Int
"""The maximum number of kilograms that this vehicle can transport."""
cargoCapacity: Float
"""
The maximum length of time that this vehicle can provide consumables for its
entire crew without having to resupply.
"""
consumables: String
pilotConnection(after: String, first: Int, before: String, last: Int): VehiclePilotsConnection
filmConnection(after: String, first: Int, before: String, last: Int): VehicleFilmsConnection
"""The ISO 8601 date format of the time that this resource was created."""
created: String
"""The ISO 8601 date format of the time that this resource was edited."""
edited: String
"""The ID of an object"""
id: ID!
}
"""A connection to a list of items."""
type VehicleFilmsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [VehicleFilmsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
films: [Film]
}
"""An edge in a connection."""
type VehicleFilmsEdge {
"""The item at the end of the edge"""
node: Film
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type VehiclePilotsConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [VehiclePilotsEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
pilots: [Person]
}
"""An edge in a connection."""
type VehiclePilotsEdge {
"""The item at the end of the edge"""
node: Person
"""A cursor for use in pagination"""
cursor: String!
}
"""A connection to a list of items."""
type VehiclesConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [VehiclesEdge]
"""
A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.
"""
totalCount: Int
"""
A list of all of the objects returned in the connection. This is a convenience
field provided for quickly exploring the API; rather than querying for
"{ edges { node } }" when no edge data is needed, this field can be be used
instead. Note that when clients like Relay need to fetch the "cursor" field on
the edge to enable efficient pagination, this shortcut cannot be used, and the
full "{ edges { node } }" version should be used instead.
"""
vehicles: [Vehicle]
}
"""An edge in a connection."""
type VehiclesEdge {
"""The item at the end of the edge"""
node: Vehicle
"""A cursor for use in pagination"""
cursor: String!
}
================================================
FILE: example/next-env.d.ts
================================================
///
///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
================================================
FILE: example/next.config.js
================================================
const relay = require('./relay.config');
module.exports = {
compiler: {
relay,
},
webpack: (config, { isServer, webpack }) => {
if (!isServer) {
// Ensures no server modules are included on the client.
config.plugins.push(new webpack.IgnorePlugin({
resourceRegExp: /lib\/server/
}));
}
return config;
},
};
================================================
FILE: example/package.json
================================================
{
"name": "example",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"generate:relay": "relay-compiler"
},
"dependencies": {
"next": "^13.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-relay": "^14.1.0",
"relay-nextjs": "^2.0.0-rc.0",
"relay-runtime": "^14.1.0"
},
"devDependencies": {
"@types/node": "^17.0.21",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"@types/react-relay": "^14.1.2",
"@types/relay-runtime": "^14.1.4",
"autoprefixer": "^10.2.5",
"graphql": "^15.5.0",
"postcss": "^8.2.9",
"relay-compiler": "^14.1.0",
"tailwindcss": "^2.1.1",
"typescript": "^4.9.3"
}
}
================================================
FILE: example/postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
================================================
FILE: example/relay.config.js
================================================
module.exports = {
src: './src',
schema: './data/schema.graphql',
exclude: ['**/node_modules/**', '**/__generated__/**'],
language: 'typescript',
artifactDirectory: 'src/queries/__generated__',
};
================================================
FILE: example/src/lib/relay_client_environment.ts
================================================
import { Environment, Network, Store, RecordSource } from 'relay-runtime';
export function createClientNetwork() {
return Network.create(async (params, variables) => {
const response = await fetch(
'https://swapi-graphql.netlify.app/.netlify/functions/index',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: params.text,
variables,
}),
}
);
return await response.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;
}
================================================
FILE: example/src/lib/server/relay_server_environment.ts
================================================
import { Environment, Network, Store, RecordSource } from 'relay-runtime';
export function createServerNetwork() {
return Network.create(async (params, variables) => {
const response = await fetch(
'https://swapi-graphql.netlify.app/.netlify/functions/index',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: params.text,
variables,
}),
}
);
return await response.json();
});
}
export function createServerEnvironment() {
return new Environment({
network: createServerNetwork(),
store: new Store(new RecordSource()),
isServer: true,
});
}
================================================
FILE: example/src/pages/_app.tsx
================================================
import { getClientEnvironment } from 'lib/relay_client_environment';
import type { AppProps } from 'next/app';
import { RelayEnvironmentProvider } from 'react-relay/hooks';
import { useRelayNextjs } from 'relay-nextjs/app';
import 'tailwindcss/tailwind.css';
function ExampleApp({ Component, pageProps }: AppProps) {
const { env, ...relayProps } = useRelayNextjs(pageProps, {
createClientEnvironment: () => getClientEnvironment()!,
});
return (
);
}
export default ExampleApp;
================================================
FILE: example/src/pages/film/[id].tsx
================================================
import { getClientEnvironment } from 'lib/relay_client_environment';
import Link from 'next/link';
import type { Id_filmDescription$key } from 'queries/__generated__/Id_filmDescription.graphql';
import type { Id_filmQuery } from 'queries/__generated__/Id_filmQuery.graphql';
import { graphql, useFragment, usePreloadedQuery } from 'react-relay';
import type { RelayProps } from 'relay-nextjs';
import { withRelay } from 'relay-nextjs';
function FilmDescription(props: { film: Id_filmDescription$key }) {
const film = useFragment(
graphql`
fragment Id_filmDescription on Film {
director
openingCrawl
}
`,
props.film
);
return (
;
}
export default withRelay(UserProfile, UserProfileQuery, {
// Fallback to render while the page is loading.
// This property is optional.
fallback: ,
// Create a Relay environment on the client-side.
// Note: This function must always return the same value.
createClientEnvironment: () => getClientEnvironment()!,
// Gets server side props for the page.
serverSideProps: async (ctx) => {
// This is an example of getting an auth token from the request context.
// If you don't need to authenticate users this can be removed and return an
// empty object instead.
const { getTokenFromCtx } = await import('lib/server/auth');
const token = await getTokenFromCtx(ctx);
if (token == null) {
return {
redirect: { destination: '/login', permanent: false },
};
}
return { token };
},
// Server-side props can be accessed as the second argument
// to this function.
createServerEnvironment: async (
ctx,
// The object returned from serverSideProps. If you don't need a token
// you can remove this argument.
{ token }: { token: string }
) => {
const { createServerEnvironment } = await import('lib/server_environment');
return createServerEnvironment(token);
},
});
```
================================================
FILE: website/docs/installation-and-setup.md
================================================
---
title: Installation and Setup
---
## Installing Relay
Relay comes with quite a number of dependencies that don't involve Next.js.
We'll set those up first before moving on to `relay-nextjs`.
First install Relay's runtime dependencies:
```
npm install react-relay relay-runtime
```
TypeScript users should install appropriate `@types` packages:
```
npm install --save-dev @types/react-relay @types/relay-runtime
```
## Configuring Relay
Install `relay-config` to provide a single configuration file to the rest of
Relay:
```
npm install --save-dev relay-config
```
Create `relay.config.js`. For Next.js projects using TypeScript this should look
something like this:
```js
module.exports = {
src: './src',
schema: './src/schema/__generated__/schema.graphql',
exclude: ['**/node_modules/**', '**/__mocks__/**', '**/__generated__/**'],
extensions: ['ts', 'tsx'],
language: 'typescript',
artifactDirectory: 'src/queries/__generated__',
};
```
### Configuring `artifactDirectory`
Next.js's `/pages` directory cannot include non-React components as default
export.
By default, the relay-compiler generates `*.graphql.ts` files that are
co-located with the corresponding files containing graphql tags. To fix this
configure `artifactDirectory` to emit to `src/queries/__generated__`:
```js:relay.config.js
module.exports = {
// ...
artifactDirectory: 'src/queries/__generated__',
}
```
For more information please see the Relay
[type emission documentation](https://relay.dev/docs/guides/type-emission/#single-artifact-directory).
Alternatively you can keep `*.graphql.ts` files in `/pages` directory with
[`pageExtensions`](https://nextjs.org/docs/api-reference/next.config.js/custom-page-extensions).
## Installing Relay Compiler
The Relay Compiler statically analyzes and optimizes GraphQL queries in your
application. To install the dependencies run:
```
npm install --save-dev relay-compiler relay-compiler-language-typescript babel-plugin-relay graphql
```
For convenience create a `package.json` to run the compiler:
```json
{
"scripts": {
"generate:relay": "relay-compiler"
}
}
```
Then configure Babel to compile away `graphql` strings:
`.babelrc`:
```json
{
"presets": ["next/babel"],
"plugins": ["relay"]
}
```
`relay-nextjs` is designed to run on both the server and client. To avoid
pulling in server dependencies to the client bundle configure Webpack to ignore
any files in `src/lib/server`. In `next.config.js`:
```js
const webpack = require("webpack");
//...
module.exports = {
webpack: (config, { isServer, webpack }) => {
if (!isServer) {
// Ensures no server modules are included on the client.
config.plugins.push(
new webpack.IgnorePlugin({ resourceRegExp: /lib\/server/ })
);
}
return config;
},
};
```
If your path to server-only files is different please adjust the above RegExp
properly.
================================================
FILE: website/docs/lazy-loaded-query.md
================================================
---
title: Lazy-loaded Queries
---
Relay's
[`useLazyLoadQuery` API](https://relay.dev/docs/api-reference/use-lazy-load-query/)
let us defer loading queries until a component is mounted. To render a loading
state while the query is pending the docs recommended adding a ``
boundary. Next.js and `relay-nextjs` both expect to be able to render on the
server and as of the time of writing React Suspense does not support server
rendering. When using `withRelay` and `usePreloadedQuery` we take care of adding
the `` boundary for you but we cannot here.
To use `useLazyLoadQuery` and render a `` boundary you must create a
dynamically rendered component that skips SSR. For example:
```tsx
// src/components/user_stats.tsx
import type { userStats_Birthday } from 'queries/__generated__/userStats_Birthday.graphql';
import React, { Suspense, useCallback } from 'react';
import { graphql, useLazyLoadQuery } from 'react-relay';
function UserBirthday({ uuid }: { uuid: string }) {
const query = useLazyLoadQuery(
graphql`
query userStats_Birthday($uuid: ID!) {
user(id: $uuid) {
birthday
}
}
`,
{ uuid }
);
return
Birthday is {query.user.birthday}!
;
}
function UserStats({ uuid }: { uuid: string }) {
return (
);
}
export default UserStats;
```
Note that we have two components here: one that has a `` boundary and
one that actually calls `useLazyLoadQuery`. If these two were merged into the
same component there would be no boundary to catch `useLazyLoadQuery`
suspending!
To render this component use the
[Next.js `dynamic` API](https://nextjs.org/docs/advanced-features/dynamic-import):
```tsx
// src/pages/user_profile.tsx
import dynamic from 'next/dynamic';
const UserStats = dynamic(() => import('components/components/user_stats'), {
ssr: false,
});
function UserProfile({ uuid }: { uuid: string }) {
return (
{/* ... */}
);
}
```
================================================
FILE: website/docs/page-api.md
================================================
---
title: Relay Page API
---
## `withRelay`
Wraps a component, GraphQL query, and a set of options to manage loading the
page and its data, as specified by the query. Example usage:
```tsx
// src/pages/user/[uuid].tsx
import { withRelay, RelayProps } from 'relay-nextjs';
import { graphql, usePreloadedQuery } from 'react-relay/hooks';
// The $uuid variable is injected automatically from the route.
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 (