Full Code of RevereCRE/relay-nextjs for AI

main b3b96fc010c1 cached
48 files
102.0 KB
27.1k tokens
38 symbols
1 requests
Download .txt
Repository: RevereCRE/relay-nextjs
Branch: main
Commit: b3b96fc010c1
Files: 48
Total size: 102.0 KB

Directory structure:
gitextract_o2atgi3y/

├── .gitignore
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── example/
│   ├── .gitignore
│   ├── .prettierignore
│   ├── .prettierrc
│   ├── README.md
│   ├── data/
│   │   └── schema.graphql
│   ├── next-env.d.ts
│   ├── next.config.js
│   ├── package.json
│   ├── postcss.config.js
│   ├── relay.config.js
│   ├── src/
│   │   ├── lib/
│   │   │   ├── relay_client_environment.ts
│   │   │   └── server/
│   │   │       └── relay_server_environment.ts
│   │   ├── pages/
│   │   │   ├── _app.tsx
│   │   │   ├── film/
│   │   │   │   └── [id].tsx
│   │   │   └── index.tsx
│   │   └── queries/
│   │       └── __generated__/
│   │           ├── Id_filmDescription.graphql.ts
│   │           ├── Id_filmQuery.graphql.ts
│   │           └── pages_listFilmsQuery.graphql.ts
│   ├── tailwind.config.js
│   └── tsconfig.json
├── package.json
├── src/
│   ├── app.ts
│   ├── component.tsx
│   ├── index.tsx
│   └── json_meta.ts
├── tsconfig.json
└── website/
    ├── .gitignore
    ├── README.md
    ├── babel.config.js
    ├── docs/
    │   ├── app-api.md
    │   ├── configuration.md
    │   ├── installation-and-setup.md
    │   ├── lazy-loaded-query.md
    │   ├── page-api.md
    │   ├── prerequisites.md
    │   └── what-why.md
    ├── docusaurus.config.js
    ├── package.json
    ├── sidebars.js
    ├── src/
    │   ├── css/
    │   │   └── custom.css
    │   └── pages/
    │       ├── index.js
    │       └── styles.module.css
    └── static/
        └── .nojekyll

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port


================================================
FILE: .prettierignore
================================================
node_modules/
dist/
package*.json

example/.next
example/src/queries/__generated__
example/data

website/node_modules
website/build
website/.docusaurus
website/.cache-loader


================================================
FILE: .prettierrc
================================================
{
  "singleQuote": true,
  "bracketSpacing": true,
  "arrowParens": "always",
  "proseWrap": "always"
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 Revere CRE

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<p align="center">
  <b>Revere CRE is hiring! Interested in working on the cutting edge of frontend?</b>
  <br>
  <b>Reach out to <a href="mailto:eng-jobs@reverecre.com">eng-jobs@reverecre.com</a> for more information.</b>
</p>

<h1 align="center">
  Relay + Next.js
</h1>

[![npm version](https://badge.fury.io/js/relay-nextjs.svg)](https://badge.fury.io/js/relay-nextjs)
![npm downloads](https://img.shields.io/npm/dm/relay-nextjs)
![npm bundle size](https://img.shields.io/bundlephobia/minzip/relay-nextjs)

<p align="center">
  <a href="https://reverecre.github.io/relay-nextjs"><b>Documentation</b></a> |
  <a href="https://github.com/RevereCRE/relay-nextjs/discussions"><b>Discussion</b></a> |
  <a href="https://github.com/RevereCRE/relay-nextjs/releases"><b>Latest Releases</b></a>
</p>
 
<p align="center">
  <code>relay-nextjs</code> is the best way to use Relay and Next.js in the same project! It supports
  <b>incremental migration</b>, is <b>suspense ready</b>, and is <b>run in production</b> by major
  companies.
</p>

## 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 (
    <>
      <RelayEnvironmentProvider environment={env}>
        <Component {...pageProps} {...relayProps} />
      </RelayEnvironmentProvider>
    </>
  );
}

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 (
    <div>
      Hello {query.user.firstName} {query.user.lastName}
    </div>
  );
}

function Loading() {
  return <div>Loading...</div>;
}

export default withRelay(UserProfile, UserProfileQuery, {
  // Fallback to render while the page is loading.
  // This property is optional.
  fallback: <Loading />,
  // 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
================================================
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// 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 (
    <RelayEnvironmentProvider environment={env}>
      <Component {...pageProps} {...relayProps} />
    </RelayEnvironmentProvider>
  );
}

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 (
    <div className="flex flex-col items-center text-center">
      <p>Directed by {film.director}</p>
      <p>{film.openingCrawl}</p>
    </div>
  );
}

const FilmQuery = graphql`
  query Id_filmQuery($id: ID!) {
    film(id: $id) {
      title

      ...Id_filmDescription
    }
  }
`;

function Film({ preloadedQuery }: RelayProps<{}, Id_filmQuery>) {
  const query = usePreloadedQuery(FilmQuery, preloadedQuery);
  if (query.film == null) return null;

  return (
    <div className="max-w-xl mx-auto p-8">
      <div className="flex items-center justify-center w-full pb-3">
        <Link href="/" className="text-blue-500 hover:underline mr-3">
          « Home
        </Link>
        <h1 className="text-center text-3xl font-semibold">
          {query.film.title}
        </h1>
      </div>

      <FilmDescription film={query.film} />
    </div>
  );
}

export default withRelay(Film, FilmQuery, {
  createClientEnvironment: () => getClientEnvironment()!,
  createServerEnvironment: async () => {
    const { createServerEnvironment } = await import(
      'lib/server/relay_server_environment'
    );

    return createServerEnvironment();
  },
  // This code is executed on the client between page transitions.
  // All props returned are passed directly to the component.
  clientSideProps: async () => {
    await new Promise((resolve) => setTimeout(resolve, 200));
    return {};
  },
});


================================================
FILE: example/src/pages/index.tsx
================================================
import { getClientEnvironment } from 'lib/relay_client_environment';
import type { pages_listFilmsQuery } from 'queries/__generated__/pages_listFilmsQuery.graphql';
import React from 'react';
import { graphql, usePreloadedQuery } from 'react-relay';
import type { RelayProps } from 'relay-nextjs';
import { withRelay } from 'relay-nextjs';
import Link from 'next/link';

const FilmListQuery = graphql`
  query pages_listFilmsQuery {
    allFilms {
      films {
        id
        title
        openingCrawl
      }
    }
  }
`;

function FilmList({ preloadedQuery }: RelayProps<{}, pages_listFilmsQuery>) {
  const query = usePreloadedQuery(FilmListQuery, preloadedQuery);
  if (query.allFilms == null || query.allFilms.films == null) return null;

  return (
    <div className="max-w-xl mx-auto p-8">
      <h1 className="text-center text-3xl font-semibold mb-5">All Films</h1>

      <div className="grid grid-cols-3 gap-3">
        {query.allFilms.films.map((film) => {
          if (film == null) return null;
          return (
            <div
              key={film.id}
              className="rounded-md h-32 bg-gray-700 text-white"
            >
              <Link href={`/film/${film.id}`}>
                <h2 className="text-center mt-3 hover:underline">
                  {film.title}
                </h2>
              </Link>
              <p className="text-sm p-2 truncate text-gray-200">
                {film.openingCrawl}
              </p>
            </div>
          );
        })}
      </div>
    </div>
  );
}

export default withRelay(FilmList, FilmListQuery, {
  createClientEnvironment: () => getClientEnvironment()!,
  createServerEnvironment: async () => {
    const { createServerEnvironment } = await import(
      'lib/server/relay_server_environment'
    );

    return createServerEnvironment();
  },
});


================================================
FILE: example/src/queries/__generated__/Id_filmDescription.graphql.ts
================================================
/**
 * @generated SignedSource<<ed9ee293b0eda0b05416355b644db88d>>
 * @lightSyntaxTransform
 * @nogrep
 */

/* tslint:disable */
/* eslint-disable */
// @ts-nocheck

import { Fragment, ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type Id_filmDescription$data = {
  readonly director: string | null;
  readonly openingCrawl: string | null;
  readonly " $fragmentType": "Id_filmDescription";
};
export type Id_filmDescription$key = {
  readonly " $data"?: Id_filmDescription$data;
  readonly " $fragmentSpreads": FragmentRefs<"Id_filmDescription">;
};

const node: ReaderFragment = {
  "argumentDefinitions": [],
  "kind": "Fragment",
  "metadata": null,
  "name": "Id_filmDescription",
  "selections": [
    {
      "alias": null,
      "args": null,
      "kind": "ScalarField",
      "name": "director",
      "storageKey": null
    },
    {
      "alias": null,
      "args": null,
      "kind": "ScalarField",
      "name": "openingCrawl",
      "storageKey": null
    }
  ],
  "type": "Film",
  "abstractKey": null
};

(node as any).hash = "0cff8413ec741c40ba2da65ea4176a9b";

export default node;


================================================
FILE: example/src/queries/__generated__/Id_filmQuery.graphql.ts
================================================
/**
 * @generated SignedSource<<09ea506460f6041b1df834301726692e>>
 * @lightSyntaxTransform
 * @nogrep
 */

/* tslint:disable */
/* eslint-disable */
// @ts-nocheck

import { ConcreteRequest, Query } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type Id_filmQuery$variables = {
  id: string;
};
export type Id_filmQuery$data = {
  readonly film: {
    readonly title: string | null;
    readonly " $fragmentSpreads": FragmentRefs<"Id_filmDescription">;
  } | null;
};
export type Id_filmQuery = {
  variables: Id_filmQuery$variables;
  response: Id_filmQuery$data;
};

const node: ConcreteRequest = (function(){
var v0 = [
  {
    "defaultValue": null,
    "kind": "LocalArgument",
    "name": "id"
  }
],
v1 = [
  {
    "kind": "Variable",
    "name": "id",
    "variableName": "id"
  }
],
v2 = {
  "alias": null,
  "args": null,
  "kind": "ScalarField",
  "name": "title",
  "storageKey": null
};
return {
  "fragment": {
    "argumentDefinitions": (v0/*: any*/),
    "kind": "Fragment",
    "metadata": null,
    "name": "Id_filmQuery",
    "selections": [
      {
        "alias": null,
        "args": (v1/*: any*/),
        "concreteType": "Film",
        "kind": "LinkedField",
        "name": "film",
        "plural": false,
        "selections": [
          (v2/*: any*/),
          {
            "args": null,
            "kind": "FragmentSpread",
            "name": "Id_filmDescription"
          }
        ],
        "storageKey": null
      }
    ],
    "type": "Root",
    "abstractKey": null
  },
  "kind": "Request",
  "operation": {
    "argumentDefinitions": (v0/*: any*/),
    "kind": "Operation",
    "name": "Id_filmQuery",
    "selections": [
      {
        "alias": null,
        "args": (v1/*: any*/),
        "concreteType": "Film",
        "kind": "LinkedField",
        "name": "film",
        "plural": false,
        "selections": [
          (v2/*: any*/),
          {
            "alias": null,
            "args": null,
            "kind": "ScalarField",
            "name": "director",
            "storageKey": null
          },
          {
            "alias": null,
            "args": null,
            "kind": "ScalarField",
            "name": "openingCrawl",
            "storageKey": null
          },
          {
            "alias": null,
            "args": null,
            "kind": "ScalarField",
            "name": "id",
            "storageKey": null
          }
        ],
        "storageKey": null
      }
    ]
  },
  "params": {
    "cacheID": "db6af483ec2e52c7c7359182fe7688db",
    "id": null,
    "metadata": {},
    "name": "Id_filmQuery",
    "operationKind": "query",
    "text": "query Id_filmQuery(\n  $id: ID!\n) {\n  film(id: $id) {\n    title\n    ...Id_filmDescription\n    id\n  }\n}\n\nfragment Id_filmDescription on Film {\n  director\n  openingCrawl\n}\n"
  }
};
})();

(node as any).hash = "3573cf75b79f7fd10e324897f10a7d3a";

export default node;


================================================
FILE: example/src/queries/__generated__/pages_listFilmsQuery.graphql.ts
================================================
/**
 * @generated SignedSource<<4b786e7bb83bbdb4f636d8f71dac9a5f>>
 * @lightSyntaxTransform
 * @nogrep
 */

/* tslint:disable */
/* eslint-disable */
// @ts-nocheck

import { ConcreteRequest, Query } from 'relay-runtime';
export type pages_listFilmsQuery$variables = {};
export type pages_listFilmsQuery$data = {
  readonly allFilms: {
    readonly films: ReadonlyArray<{
      readonly id: string;
      readonly title: string | null;
      readonly openingCrawl: string | null;
    } | null> | null;
  } | null;
};
export type pages_listFilmsQuery = {
  variables: pages_listFilmsQuery$variables;
  response: pages_listFilmsQuery$data;
};

const node: ConcreteRequest = (function(){
var v0 = [
  {
    "alias": null,
    "args": null,
    "concreteType": "FilmsConnection",
    "kind": "LinkedField",
    "name": "allFilms",
    "plural": false,
    "selections": [
      {
        "alias": null,
        "args": null,
        "concreteType": "Film",
        "kind": "LinkedField",
        "name": "films",
        "plural": true,
        "selections": [
          {
            "alias": null,
            "args": null,
            "kind": "ScalarField",
            "name": "id",
            "storageKey": null
          },
          {
            "alias": null,
            "args": null,
            "kind": "ScalarField",
            "name": "title",
            "storageKey": null
          },
          {
            "alias": null,
            "args": null,
            "kind": "ScalarField",
            "name": "openingCrawl",
            "storageKey": null
          }
        ],
        "storageKey": null
      }
    ],
    "storageKey": null
  }
];
return {
  "fragment": {
    "argumentDefinitions": [],
    "kind": "Fragment",
    "metadata": null,
    "name": "pages_listFilmsQuery",
    "selections": (v0/*: any*/),
    "type": "Root",
    "abstractKey": null
  },
  "kind": "Request",
  "operation": {
    "argumentDefinitions": [],
    "kind": "Operation",
    "name": "pages_listFilmsQuery",
    "selections": (v0/*: any*/)
  },
  "params": {
    "cacheID": "de4a2a5dbbdb78fb7ded1409cb88a921",
    "id": null,
    "metadata": {},
    "name": "pages_listFilmsQuery",
    "operationKind": "query",
    "text": "query pages_listFilmsQuery {\n  allFilms {\n    films {\n      id\n      title\n      openingCrawl\n    }\n  }\n}\n"
  }
};
})();

(node as any).hash = "26d1e122fd6b9b275c2ded9ee0a59a18";

export default node;


================================================
FILE: example/tailwind.config.js
================================================
module.exports = {
  purge: [
    './src/pages/**/*.{js,ts,jsx,tsx}',
    './src/components/**/*.{js,ts,jsx,tsx}',
  ],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
};


================================================
FILE: example/tsconfig.json
================================================
{
  "compilerOptions": {
    "allowJs": false,
    "allowUnusedLabels": false,
    "baseUrl": "./src",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "lib": ["dom", "dom.iterable", "esnext"],
    "module": "esnext",
    "moduleResolution": "node",
    "noEmit": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitReturns": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "es5",
    "incremental": true
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}


================================================
FILE: package.json
================================================
{
  "name": "relay-nextjs",
  "version": "3.0.1",
  "description": "Use Relay in your Next.js apps!",
  "main": "index.js",
  "scripts": {
    "build": "tsc && cp -r dist/* . && rm -rf dist",
    "clean": "rm *.js *.d.ts",
    "format": "prettier --write .",
    "check": "npm run check:prettier",
    "check:prettier": "prettier --check ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/RevereCRE/relay-nextjs.git"
  },
  "keywords": [
    "relay",
    "next",
    "nextjs",
    "graphql"
  ],
  "author": "Ryan Delaney <rrdelaney@outlook.com>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/RevereCRE/relay-nextjs/issues"
  },
  "homepage": "https://github.com/RevereCRE/relay-nextjs#readme",
  "devDependencies": {
    "@types/lodash.isequal": "^4.5.6",
    "@types/node": "^14.14.41",
    "@types/react": "^17.0.3",
    "@types/react-dom": "^17.0.3",
    "@types/react-relay": "^11.0.0",
    "@types/relay-runtime": "^10.1.10",
    "@types/serialize-javascript": "^5.0.0",
    "graphql": "^15.5.0",
    "next": "^10.0.9",
    "prettier": "^2.2.1",
    "typescript": "^5.0.4"
  },
  "dependencies": {
    "lodash.isequal": "^4.5.0",
    "serialize-javascript": "^5.0.1"
  },
  "files": [
    "*.js",
    "*.d.ts"
  ]
}


================================================
FILE: src/app.ts
================================================
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
import { useMemo, useState } from 'react';
import type { Environment } from 'react-relay';
import { loadQuery } from 'react-relay';
import type { GraphQLSingularResponse } from 'relay-runtime';
import type { AnyPreloadedQuery, UseRelayNextJsProps } from './component';
import { hydrateObject } from './json_meta';

export function useRelayNextjs(
  props: UseRelayNextJsProps,
  opts: { createClientEnvironment: () => Environment }
): { env: Environment; preloadedQuery?: AnyPreloadedQuery; CSN: boolean } {
  const [relayEnvironment] = useState(() => {
    if (props.preloadedQuery?.environment) {
      return props.preloadedQuery.environment;
    }

    const env = opts.createClientEnvironment();
    if (props.payload && props.payloadMeta && props.operationDescriptor) {
      hydrateObject(props.payloadMeta, props.payload);

      // After SSR, during initial render (hydration), the store is empty.
      // `getInitialProps` from the server gives us data to "replay" the data
      // fetching allowing us to "hydrate" the store, ensuring the initial
      // render matches the server's.
      env.commitPayload(
        props.operationDescriptor,
        (props.payload as GraphQLSingularResponse).data!
      );
    }

    return env;
  });

  const preloadedQuery = useMemo(() => {
    if (props.preloadedQuery) {
      // During SSR and client-side navigations this will be defined.
      return props.preloadedQuery;
    } else if (props.operationDescriptor) {
      // During initial hydration we don't have a reference to the preloadedQuery
      // from the server because it cannot be serialized. In that case we recreate
      // it from store data.
      return loadQuery(
        relayEnvironment,
        props.operationDescriptor.request.node,
        props.operationDescriptor.request.variables,
        { fetchPolicy: 'store-or-network' }
      );
    } else {
      // If the page we landed on doesn't have these props defined it is not a
      // page using relay-nextjs, so we should just return undefined.
      return undefined;
    }
  }, [props, relayEnvironment]);

  return { env: relayEnvironment, preloadedQuery, CSN: props.CSN };
}


================================================
FILE: src/component.tsx
================================================
import isEqual from 'lodash.isequal';
import type { NextPageContext, Redirect } from 'next';
import Router, { NextRouter, useRouter } from 'next/router';
import {
  ComponentType,
  ReactNode,
  Suspense,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import {
  LoadQueryOptions,
  PreloadedQuery,
  loadQuery,
  useRelayEnvironment,
} from 'react-relay';
import {
  ConcreteRequest,
  Environment,
  GraphQLResponse,
  GraphQLTaggedNode,
  OperationDescriptor,
  OperationType,
  RelayFeatureFlags,
  Variables,
  createOperationDescriptor,
} from 'relay-runtime';
import { HydrationMeta, collectMeta } from './json_meta';

export type AnyPreloadedQuery = PreloadedQuery<OperationType>;

// Enabling this feature flag to determine if a page should 404 on the server.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(RelayFeatureFlags as any).ENABLE_REQUIRED_DIRECTIVES = true;

export type RelayProps<
  P extends {} = {},
  Q extends OperationType = OperationType
> = P & Required<Pick<UseRelayNextJsProps<P, Q>, 'CSN' | 'preloadedQuery'>>;

export type UseRelayNextJsProps<
  P extends {} = {},
  Q extends OperationType = OperationType
> = P & {
  /** If this page rendering resulted from a client-side navigation. */
  CSN: boolean;
  /** Undefined during initial hydration, but defined for SSR and subsequent renders */
  preloadedQuery?: PreloadedQuery<Q>;
  operationDescriptor?: OperationDescriptor;
  payload?: GraphQLResponse;
  payloadMeta?: HydrationMeta;
};

export type OrRedirect<T> = T | { redirect: Redirect };

export interface RelayOptions<
  Props extends RelayProps,
  ServerSideProps extends {} = {}
> {
  /** Fallback rendered when the page suspends. */
  fallback?: ReactNode;
  variablesFromContext?: (
    ctx: NextPageContext | NextRouter
  ) => Props['preloadedQuery']['variables'];
  queryOptionsFromContext?: (
    ctx: NextPageContext | NextRouter
  ) => LoadQueryOptions;
  /** Called when creating a Relay environment on the client. Should be idempotent. */
  createClientEnvironment: () => Environment;
  /** Props passed to the component when rendering on the client. */
  clientSideProps?: (
    ctx: NextPageContext
  ) => OrRedirect<Partial<ServerSideProps>>;
  /** Called when creating a Relay environment on the server. */
  createServerEnvironment: (
    ctx: NextPageContext,
    props: ServerSideProps
  ) => Promise<Environment>;
  /** Props passed to the component when rendering on the server. */
  serverSideProps?: (
    ctx: NextPageContext
  ) => Promise<OrRedirect<ServerSideProps>>;
  /** Runs after a query has been executed on the server. */
  serverSidePostQuery?: (
    queryResult: GraphQLResponse | undefined,
    ctx: NextPageContext
  ) => Promise<unknown> | unknown;
}

function defaultVariablesFromContext(
  ctx: NextPageContext | NextRouter
): Variables {
  return ctx.query;
}

function defaultQueryOptionsFromContext(
  _ctx: NextPageContext | NextRouter
): LoadQueryOptions {
  return { fetchPolicy: 'store-or-network' };
}

function useStableIdentity<T>(nextValue: T): T {
  const lastValue = useRef<T>(nextValue);
  return useMemo((): T => {
    if (!isEqual(lastValue.current, nextValue)) {
      lastValue.current = nextValue;
    }

    return lastValue.current;
  }, [nextValue]);
}

export function withRelay<Props extends RelayProps, ServerSideProps extends {}>(
  Component: ComponentType<Props>,
  query: GraphQLTaggedNode,
  opts: RelayOptions<Props, ServerSideProps>
) {
  const {
    queryOptionsFromContext = defaultQueryOptionsFromContext,
    variablesFromContext = defaultVariablesFromContext,
  } = opts;

  function useLoadedQuery(initialPreloadedQuery: Props['preloadedQuery']) {
    const router = useRouter();

    const queryOptions = useStableIdentity(
      useMemo(() => queryOptionsFromContext(router), [router])
    );

    const queryVariables = useStableIdentity(
      useMemo(() => variablesFromContext(router), [router])
    );

    const [preloadedQuery, setPreloadedQuery] = useState(initialPreloadedQuery);

    const isMountedRef = useRef(false);
    const env = useRelayEnvironment();
    useEffect(() => {
      // Avoid re-setting the initial preloaded query on the first render.
      if (!isMountedRef.current) {
        isMountedRef.current = true;
        return;
      }

      const nextPreloadedQuery = loadQuery(
        env,
        query,
        queryVariables,
        queryOptions
      );

      setPreloadedQuery(nextPreloadedQuery);
      return () => nextPreloadedQuery.dispose();
    }, [env, queryVariables, queryOptions]);

    return preloadedQuery;
  }

  function RelayComponent(props: Props) {
    const preloadedQuery = useLoadedQuery(props.preloadedQuery);
    return (
      <Suspense fallback={opts.fallback ?? 'Loading...'}>
        <Component {...props} preloadedQuery={preloadedQuery} />
      </Suspense>
    );
  }

  RelayComponent.getInitialProps = relayInitialProps(query, opts);

  return RelayComponent;
}

function relayInitialProps<
  Props extends RelayProps,
  ServerSideProps extends {}
>(query: GraphQLTaggedNode, opts: RelayOptions<Props, ServerSideProps>) {
  return async (ctx: NextPageContext): Promise<UseRelayNextJsProps> => {
    if (typeof window === 'undefined') {
      return getServerInitialProps(ctx, query, opts);
    } else {
      return getClientInitialProps(ctx, query, opts);
    }
  };
}

async function getServerInitialProps<
  Props extends RelayProps,
  ServerSideProps extends {}
>(
  ctx: NextPageContext,
  query: GraphQLTaggedNode,
  opts: RelayOptions<Props, ServerSideProps>
): Promise<UseRelayNextJsProps> {
  const {
    variablesFromContext = defaultVariablesFromContext,
    queryOptionsFromContext = defaultQueryOptionsFromContext,
  } = opts;

  const serverSideProps = opts.serverSideProps
    ? await opts.serverSideProps(ctx)
    : ({} as ServerSideProps);

  if ('redirect' in serverSideProps) {
    const { redirect } = serverSideProps;

    let statusCode = 302;
    if ('statusCode' in redirect) {
      statusCode = redirect.statusCode;
    } else if ('permanent' in redirect) {
      statusCode = redirect.permanent ? 308 : 307;
    }

    ctx
      .res!.writeHead(statusCode, {
        Location: redirect.destination,
      })
      .end();

    return { CSN: false };
  }

  const env = await opts.createServerEnvironment(ctx, serverSideProps);
  const variables = variablesFromContext(ctx);
  const queryOptions = queryOptionsFromContext(ctx);
  const preloadedQuery = loadQuery(env, query, variables, queryOptions);

  const payload = await ensureQueryFlushed(preloadedQuery);
  await opts.serverSidePostQuery?.(payload, ctx);

  const payloadSerializationMetadata = collectMeta(payload);

  const request = query as { default: ConcreteRequest } | ConcreteRequest;
  const operationDescriptor = createOperationDescriptor(
    'default' in request ? request.default : request,
    variables
  );

  const props: UseRelayNextJsProps = {
    ...serverSideProps,
    CSN: false,
    operationDescriptor,
    payload,
    payloadMeta: payloadSerializationMetadata,
  };

  // This will only be available during SSR, not during client side render or hydration.
  Object.defineProperty(props, 'preloadedQuery', {
    enumerable: false,
    value: preloadedQuery,
  });

  return props;
}

async function getClientInitialProps<
  Props extends RelayProps,
  ClientSideProps extends {}
>(
  ctx: NextPageContext,
  query: GraphQLTaggedNode,
  opts: RelayOptions<Props, ClientSideProps>
): Promise<UseRelayNextJsProps> {
  const {
    variablesFromContext = defaultVariablesFromContext,
    queryOptionsFromContext = defaultQueryOptionsFromContext,
  } = opts;

  const clientSideProps = opts.clientSideProps
    ? opts.clientSideProps(ctx)
    : ({} as ClientSideProps);

  if ('redirect' in clientSideProps) {
    void Router.push(clientSideProps.redirect.destination);
    return { CSN: true };
  }

  const env = opts.createClientEnvironment();
  const variables = variablesFromContext(ctx);
  const queryOptions = queryOptionsFromContext(ctx);
  const preloadedQuery = loadQuery(env, query, variables, queryOptions);

  return {
    ...clientSideProps,
    CSN: true,
    preloadedQuery,
  };
}

async function ensureQueryFlushed(
  query: AnyPreloadedQuery
): Promise<GraphQLResponse> {
  return new Promise((resolve, reject) => {
    if (query.source == null) {
      resolve({ data: {} });
    } else {
      query.source.subscribe({
        next: resolve,
        error: reject,
      });
    }
  });
}


================================================
FILE: src/index.tsx
================================================
export { withRelay } from './component';
export type { OrRedirect, RelayOptions, RelayProps } from './component';


================================================
FILE: src/json_meta.ts
================================================
/**
 * @fileoverview Functions for extracting and applying serialization metadata
 * to JSON. When a JS object is serialized to JSON it will call the `toJSON`
 * and `toString` methods on objects. When deserializing the original types of
 * those values are lost. This library exports `collectMeta` which returns a
 * JSON-serializable representation of how the original object will be
 * serialized. This can be passed to `hydrateObject` to restore the
 * original object. Currently supports `Date` and `URL` values.
 */

/** Type of value that can be restored. */
export enum EncodedType {
  DATE = 'date',
  URL = 'url',
}

export type HydrationMeta = Record<string, EncodedType>;

/**
 * Restores the type of values after deserializing a JSON object. Meta must be
 * the object returned from `collectMeta`. Mutates the object in place.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function hydrateObject(meta: HydrationMeta, value: any) {
  for (const [path, encoding] of Object.entries(meta)) {
    let updatingValue = value;
    const parts = path.split('.');

    for (let i = 0; i < parts.length - 1; ++i) {
      let accessor: string | number = Number(parts[i]);
      if (Number.isNaN(accessor)) {
        accessor = parts[i]!;
      }

      updatingValue = updatingValue[accessor];
    }

    let lastAccessor: string | number = Number(parts[parts.length - 1]);
    if (Number.isNaN(lastAccessor)) {
      lastAccessor = parts[parts.length - 1]!;
    }

    switch (encoding) {
      case EncodedType.DATE:
        updatingValue[lastAccessor] = new Date(updatingValue[lastAccessor]);
        break;
      case EncodedType.URL:
        updatingValue[lastAccessor] = new URL(updatingValue[lastAccessor]);
        break;
      default:
        checkExhaustive(encoding);
    }
  }
}

function* walk(): Generator<
  string,
  void,
  { parent: {}; key: string; value: unknown }
> {
  const path: [string, unknown][] = [];

  do {
    const { parent, key, value } = yield path.map(([key]) => key).join('.');
    while (path.length > 0 && path[path.length - 1]![1] !== parent) {
      path.pop();
    }

    path.push([key, value]);
  } while (path.length > 0);
}

function createReplacer() {
  const meta: HydrationMeta = {};
  const walker = walk();

  function replacer(
    this: Record<string, unknown>,
    key: string,
    value: unknown
  ) {
    const path = walker.next({ parent: this, key, value }).value;
    if (path && this[key] instanceof Date) {
      meta[path] = EncodedType.DATE;
    } else if (path && this[key] instanceof URL) {
      meta[path] = EncodedType.URL;
    }

    return value;
  }

  return [replacer, () => meta] as const;
}

/**
 * Walks an object and extracts information for hydrating it after
 * deserialization.
 */
export function collectMeta(obj: unknown) {
  const [replacer, dumpMeta] = createReplacer();
  JSON.stringify(obj, replacer);
  return dumpMeta();
}

function checkExhaustive(cased: never): asserts cased is never {
  if (process.env.NODE_ENV !== 'production') {
    throw new Error(`Unexpected condition: ${cased}`);
  }

  return undefined;
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "module": "CommonJS",
    "target": "ES2015",
    "jsx": "react-jsx",
    "outDir": "dist",
    "declaration": true,
    "allowJs": false,
    "allowUnusedLabels": false,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "lib": ["dom", "dom.iterable", "esnext"],
    "moduleResolution": "node",
    "noFallthroughCasesInSwitch": true,
    "noImplicitReturns": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true
  },
  "include": ["src"]
}


================================================
FILE: website/.gitignore
================================================
# Dependencies
/node_modules

# Production
/build

# Generated files
.docusaurus
.cache-loader

# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*


================================================
FILE: website/README.md
================================================
# Website

This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.

## Installation

```console
yarn install
```

## Local Development

```console
yarn start
```

This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.

## Build

```console
yarn build
```

This command generates static content into the `build` directory and can be served using any static contents hosting service.

## Deployment

```console
GIT_USER=<Your GitHub username> USE_SSH=true yarn deploy
```

If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.


================================================
FILE: website/babel.config.js
================================================
module.exports = {
  presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};


================================================
FILE: website/docs/app-api.md
================================================
---
title: Relay App API
---

`relay-nextjs/app` exposes a single hook to configure your app to use Relay.

## `useRelayNextjs`

Returns an object containing an `Environment` and props needed to render a page
using `relay-nextjs`. Example usage:

```tsx
// src/pages/_app.tsx
import { RelayEnvironmentProvider } from 'react-relay/hooks';
import { useRelayNextjs } from 'relay-nextjs/app';

// This function should return a RelayEnvironment pointed at your GraphQL API.
// Note that this should always return the same object, **not** create a new
// RelayEnvironment on every call.
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;
```


================================================
FILE: website/docs/configuration.md
================================================
---
title: Configuring relay-nextjs
---

## Installing `relay-nextjs`

Install using npm or your other favorite package manager:

```
npm install relay-nextjs
```

## Routing Integration

`relay-nextjs` must be configured in a custom `_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 let’s 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';

// Relay is not prescriptive about how GraphQL requests are made.
// This is an example showing how to request GraphQL data.
// You should fill this in with how to make requests to your GraphQL
// API of choice.
import { makeGraphQLRequest } from './my_graphql_api';

export function createServerNetwork() {
  return Network.create(async (text, variables) => {
    const results = await makeGraphQLRequest(text, variables);

    return JSON.parse(JSON.stringify(results)) as GraphQLResponse;
  });
}

// Optional: this function can take a token used for authentication and pass it into `createServerNetwork`.
export function createServerEnvironment() {
  return new Environment({
    network: createServerNetwork(),
    store: new Store(new RecordSource()),
    isServer: true,
  });
}
```

### 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 (
    <>
      <RelayEnvironmentProvider environment={env}>
        <Component {...pageProps} {...relayProps} />
      </RelayEnvironmentProvider>
    </>
  );
}

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 (
    <div>
      Hello {query.user.firstName} {query.user.lastName}
    </div>
  );
}

function Loading() {
  return <div>Loading...</div>;
}

export default withRelay(UserProfile, UserProfileQuery, {
  // Fallback to render while the page is loading.
  // This property is optional.
  fallback: <Loading />,
  // 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 `<Suspense>`
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 `<Suspense>` boundary for you but we cannot here.

To use `useLazyLoadQuery` and render a `<Suspense>` 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<userStats_Birthday>(
    graphql`
      query userStats_Birthday($uuid: ID!) {
        user(id: $uuid) {
          birthday
        }
      }
    `,
    { uuid }
  );

  return <div>Birthday is {query.user.birthday}!</div>;
}

function UserStats({ uuid }: { uuid: string }) {
  return (
    <Suspense fallback="Loading...">
      <UserBirthday uuid={uuid} />
    </Suspense>
  );
}

export default UserStats;
```

Note that we have two components here: one that has a `<Suspense>` 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 (
    <div>
      {/* ... */}
      <UserStats uuid={uuid} />
    </div>
  );
}
```


================================================
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 (
    <div>
      Hello {query.user.firstName} {query.user.lastName}
    </div>
  );
}

export default withRelay(UserProfile, UserProfileQuery, options);
```

### Arguments

- `component`: A
  [Next.js page component](https://nextjs.org/docs/basic-features/pages) to
  recieve the preloaded query from `relay-nextjs`.
- `query`: A GraphQL query using the `graphql` tag from Relay.
- `options`: A [`RelayOptions`](#relayoptions) object.

## `RelayOptions`

Interface for configuring `withRelay`. Example usage:

```tsx
const options: RelayOptions<{ token: string }> = {
  fallback: <Loading />,
  queryOptionsFromContext: () => ({ fetchPolicy: 'store-and-network' }),
  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 }) => {
    const { createServerEnvironment } = await import('lib/server_environment');
    return createServerEnvironment(token);
  },
};
```

### Properties

- `fallback?`: React component to use as a loading indicator. See
  [React Suspense docs](https://reactjs.org/docs/concurrent-mode-suspense.html).
- `queryOptionsFromContext?`: Load query configuration. Defaults to
  `{ fetchPolicy: 'store-or-network' }`. See
  [Relay docs](https://relay.dev/docs/api-reference/use-lazy-load-query/#arguments)
  for more information.
- `clientSideProps?`: Provides props to the page on client-side navigations. Not
  required.
- `createClientEnvironment`: A function that returns a `RelayEnvironment`.
  Should return the same environment each time it is called.
- `serverSideProps?`: Fetch any server-side only props such as authentication
  tokens. Note that you should import server-only deps with
  `await import('...')`.
- `createServerEnvironment`: A function that returns a `RelayEnvironment`. First
  argument is `NextPageContext` and the second is the object returned by
  `serverSideProps`.
- `variablesFromContext?`: Function that extracts GraphQL query variables from
  `NextPageContext`. Run on both the client and server. If omitted query
  variables are set to `ctx.query`.
- `serverSidePostQuery?`: Function that is called during server side rendering
  after fetching the query is finished. First parameter gives you access to the
  data returned by your query and the second parameter gives access to
  `NextPageContext`. This function can be used for example to set your response
  status to 404 if your query didn't return data.


================================================
FILE: website/docs/prerequisites.md
================================================
---
title: Prerequisites
---

First check out the [Relay docs](https://relay.dev) and the
[Relay prerequisites](https://relay.dev/docs/getting-started/prerequisites/).
Then make sure you are ready with each of the following.

## A Next.js project using [Page Router](https://nextjs.org/docs/pages)

:::info

`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.

:::

`relay-nextjs` is meant to integrate the Relay framework with Next.js. If you're
not using Next.js you don't need this project. The rest of this guide will
assume your project is using **TypeScript** and the page to your pages is
`src/pages`.

Relay generates artifacts in a single directory. To avoid traversing directories
as much it will be helpful to configure TypeScript with the following `baseUrl`:

```json
// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./src"
  }
}
```

## A GraphQL API and Schema

Relay uses a GraphQL API to fetch data and compiles queries against a GraphQL
schema. This guide assumes a local schema (a `.graphql` file). To set up Relay
with a remote schema please see the
[Relay Compiler docs](https://relay.dev/docs/guides/compiler/).


================================================
FILE: website/docs/what-why.md
================================================
---
title: What is relay-nextjs
slug: /
---

`relay-nextjs` is a bridge between [Next.js](https://nextjs.org/) and
[Relay](https://relay.dev). Because these are two highly opinionated frameworks
it can be difficult to get them to work together. This library solves that
problem by glueing the two together using techniques that require low-level
knowledge of both frameworks.

Basically we wrote the glue code for you!

This library has several goals:

- Use Relay with no modifications, be able to copy code exactly as is from the
  docs
- Use only documented public APIs of React, Relay, and Next.js to prevent
  lock-in
- Does not require modifications to pages not using Relay
- Incremental adoption across your app


================================================
FILE: website/docusaurus.config.js
================================================
/** @type {import('@docusaurus/types').DocusaurusConfig} */
module.exports = {
  title: 'relay-nextjs',
  tagline: 'Relay Hooks integration for Next.js apps',
  url: 'https://reverecre.github.io',
  baseUrl: '/relay-nextjs/',
  organizationName: 'RevereCRE',
  projectName: 'relay-nextjs',
  trailingSlash: false,
  onBrokenLinks: 'throw',
  onBrokenMarkdownLinks: 'warn',
  favicon: 'img/favicon.ico',
  organizationName: 'RevereCRE', // Usually your GitHub org/user name.
  projectName: 'relay-nextjs', // Usually your repo name.
  themeConfig: {
    navbar: {
      title: 'relay-nextjs',
      items: [
        {
          to: 'docs/',
          activeBasePath: 'docs',
          label: 'Docs',
          position: 'left',
        },
        {
          href: 'https://github.com/RevereCRE/relay-nextjs',
          label: 'GitHub',
          position: 'right',
        },
      ],
    },
    footer: {
      copyright: `Copyright © ${new Date().getFullYear()} Revere CRE, Inc. Built with Docusaurus.`,
    },
  },
  presets: [
    [
      '@docusaurus/preset-classic',
      {
        docs: {
          sidebarPath: require.resolve('./sidebars.js'),
          editUrl:
            'https://github.com/RevereCRE/relay-nextjs/edit/main/website/',
        },
        theme: {
          customCss: require.resolve('./src/css/custom.css'),
        },
      },
    ],
  ],
};


================================================
FILE: website/package.json
================================================
{
  "name": "website",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "docusaurus": "docusaurus",
    "start": "docusaurus start",
    "build": "docusaurus build",
    "swizzle": "docusaurus swizzle",
    "deploy": "docusaurus deploy",
    "clear": "docusaurus clear",
    "serve": "docusaurus serve",
    "write-translations": "docusaurus write-translations",
    "write-heading-ids": "docusaurus write-heading-ids"
  },
  "browserslist": {
    "production": [
      ">0.5%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "dependencies": {
    "@docusaurus/core": "^2.4.1",
    "@docusaurus/preset-classic": "^2.4.1",
    "@mdx-js/react": "^1.6.22",
    "clsx": "^1.2.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  }
}


================================================
FILE: website/sidebars.js
================================================
module.exports = {
  docs: [
    {
      type: 'category',
      label: 'Getting Started',
      items: [
        'what-why',
        'prerequisites',
        'installation-and-setup',
        'configuration',
      ],
    },
    {
      type: 'category',
      label: 'Guides',
      items: ['lazy-loaded-query'],
    },
    {
      type: 'category',
      label: 'API Reference',
      items: ['page-api', 'app-api'],
    },
  ],
};


================================================
FILE: website/src/css/custom.css
================================================
/* stylelint-disable docusaurus/copyright-header */
/**
 * Any CSS included here will be global. The classic template
 * bundles Infima by default. Infima is a CSS framework designed to
 * work well for content-centric websites.
 */

/* You can override the default Infima variables here. */
:root {
  --ifm-color-primary: #0074b8;
  --ifm-color-primary-dark: #0091e6;
  --ifm-color-primary-darker: rgb(31, 165, 136);
  --ifm-color-primary-darkest: rgb(26, 136, 112);
  --ifm-color-primary-light: rgb(70, 203, 174);
  --ifm-color-primary-lighter: rgb(102, 212, 189);
  --ifm-color-primary-lightest: rgb(146, 224, 208);
  --ifm-code-font-size: 95%;
}

.docusaurus-highlight-code-line {
  background-color: rgb(72, 77, 91);
  display: block;
  margin: 0 calc(-1 * var(--ifm-pre-padding));
  padding: 0 var(--ifm-pre-padding);
}


================================================
FILE: website/src/pages/index.js
================================================
import React from 'react';
import clsx from 'clsx';
import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useBaseUrl from '@docusaurus/useBaseUrl';
import styles from './styles.module.css';

const features = [
  {
    title: 'Integrated with Next.js',
    imageUrl: 'img/undraw_next_js_8g5m.svg',
    description: (
      <>
        <code>relay-nextjs</code> was designed with Next.js in mind from the
        start. Perfectly integrated with Relay Hooks and no need to change pages
        not using it. 100% backwards compatible.
      </>
    ),
  },
  {
    title: 'Designed for Server Rendering',
    imageUrl: 'img/undraw_Server_re_twwj.svg',
    description: (
      <>
        Next.js speeds your page loads up using server-side rendering but
        subsequent navigations require a server-round trip.{' '}
        <code>relay-nextjs</code> keeps client-side navigation snappy with all
        the performance of SSR.
      </>
    ),
  },
  {
    title: 'Unlock React Suspense',
    imageUrl: 'img/undraw_react_y7wq.svg',
    description: (
      <>
        <code>relay-nextjs</code> uses React Suspense under the hood to
        orchestrate loading states across the page. Don't worry — we only use
        publicly documented APIs so you won't be stranded in two years.
      </>
    ),
  },
];

function Feature({ imageUrl, title, description }) {
  const imgUrl = useBaseUrl(imageUrl);
  return (
    <div className={clsx('col col--4', styles.feature)}>
      {imgUrl && (
        <div className="text--center">
          <img className={styles.featureImage} src={imgUrl} alt={title} />
        </div>
      )}
      <h3>{title}</h3>
      <p>{description}</p>
    </div>
  );
}

export default function Home() {
  const context = useDocusaurusContext();
  const { siteConfig = {} } = context;
  return (
    <Layout title={`Relay for Next.js`}>
      <header className={clsx('hero hero--primary', styles.heroBanner)}>
        <div className="container">
          <h1 className="hero__title">{siteConfig.title}</h1>
          <p className="hero__subtitle">{siteConfig.tagline}</p>
          <div className={styles.buttons}>
            <Link
              className={clsx(
                'button button--outline button--secondary button--lg',
                styles.getStarted
              )}
              to={useBaseUrl('docs/')}
            >
              Get Started
            </Link>
          </div>
        </div>
      </header>
      <main>
        {features && features.length > 0 && (
          <section className={styles.features}>
            <div className="container">
              <div className="row">
                {features.map((props, idx) => (
                  <Feature key={idx} {...props} />
                ))}
              </div>
            </div>
          </section>
        )}
      </main>
    </Layout>
  );
}


================================================
FILE: website/src/pages/styles.module.css
================================================
/* stylelint-disable docusaurus/copyright-header */

/**
 * CSS files with the .module.css suffix will be treated as CSS modules
 * and scoped locally.
 */

.heroBanner {
  padding: 4rem 0;
  text-align: center;
  position: relative;
  overflow: hidden;
}

@media screen and (max-width: 966px) {
  .heroBanner {
    padding: 2rem;
  }
}

.buttons {
  display: flex;
  align-items: center;
  justify-content: center;
}

.features {
  display: flex;
  align-items: center;
  padding: 2rem 0;
  width: 100%;
}

.featureImage {
  height: 200px;
  width: 200px;
}


================================================
FILE: website/static/.nojekyll
================================================
Download .txt
gitextract_o2atgi3y/

├── .gitignore
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── example/
│   ├── .gitignore
│   ├── .prettierignore
│   ├── .prettierrc
│   ├── README.md
│   ├── data/
│   │   └── schema.graphql
│   ├── next-env.d.ts
│   ├── next.config.js
│   ├── package.json
│   ├── postcss.config.js
│   ├── relay.config.js
│   ├── src/
│   │   ├── lib/
│   │   │   ├── relay_client_environment.ts
│   │   │   └── server/
│   │   │       └── relay_server_environment.ts
│   │   ├── pages/
│   │   │   ├── _app.tsx
│   │   │   ├── film/
│   │   │   │   └── [id].tsx
│   │   │   └── index.tsx
│   │   └── queries/
│   │       └── __generated__/
│   │           ├── Id_filmDescription.graphql.ts
│   │           ├── Id_filmQuery.graphql.ts
│   │           └── pages_listFilmsQuery.graphql.ts
│   ├── tailwind.config.js
│   └── tsconfig.json
├── package.json
├── src/
│   ├── app.ts
│   ├── component.tsx
│   ├── index.tsx
│   └── json_meta.ts
├── tsconfig.json
└── website/
    ├── .gitignore
    ├── README.md
    ├── babel.config.js
    ├── docs/
    │   ├── app-api.md
    │   ├── configuration.md
    │   ├── installation-and-setup.md
    │   ├── lazy-loaded-query.md
    │   ├── page-api.md
    │   ├── prerequisites.md
    │   └── what-why.md
    ├── docusaurus.config.js
    ├── package.json
    ├── sidebars.js
    ├── src/
    │   ├── css/
    │   │   └── custom.css
    │   └── pages/
    │       ├── index.js
    │       └── styles.module.css
    └── static/
        └── .nojekyll
Download .txt
SYMBOL INDEX (38 symbols across 12 files)

FILE: example/src/lib/relay_client_environment.ts
  function createClientNetwork (line 3) | function createClientNetwork() {
  function getClientEnvironment (line 24) | function getClientEnvironment() {

FILE: example/src/lib/server/relay_server_environment.ts
  function createServerNetwork (line 3) | function createServerNetwork() {
  function createServerEnvironment (line 23) | function createServerEnvironment() {

FILE: example/src/pages/_app.tsx
  function ExampleApp (line 7) | function ExampleApp({ Component, pageProps }: AppProps) {

FILE: example/src/pages/film/[id].tsx
  function FilmDescription (line 9) | function FilmDescription(props: { film: Id_filmDescription$key }) {
  function Film (line 38) | function Film({ preloadedQuery }: RelayProps<{}, Id_filmQuery>) {

FILE: example/src/pages/index.tsx
  function FilmList (line 21) | function FilmList({ preloadedQuery }: RelayProps<{}, pages_listFilmsQuer...

FILE: example/src/queries/__generated__/Id_filmDescription.graphql.ts
  type Id_filmDescription$data (line 13) | type Id_filmDescription$data = {
  type Id_filmDescription$key (line 18) | type Id_filmDescription$key = {

FILE: example/src/queries/__generated__/Id_filmQuery.graphql.ts
  type Id_filmQuery$variables (line 13) | type Id_filmQuery$variables = {
  type Id_filmQuery$data (line 16) | type Id_filmQuery$data = {
  type Id_filmQuery (line 22) | type Id_filmQuery = {

FILE: example/src/queries/__generated__/pages_listFilmsQuery.graphql.ts
  type pages_listFilmsQuery$variables (line 12) | type pages_listFilmsQuery$variables = {};
  type pages_listFilmsQuery$data (line 13) | type pages_listFilmsQuery$data = {
  type pages_listFilmsQuery (line 22) | type pages_listFilmsQuery = {

FILE: src/app.ts
  function useRelayNextjs (line 9) | function useRelayNextjs(

FILE: src/component.tsx
  type AnyPreloadedQuery (line 32) | type AnyPreloadedQuery = PreloadedQuery<OperationType>;
  type RelayProps (line 38) | type RelayProps<
  type UseRelayNextJsProps (line 43) | type UseRelayNextJsProps<
  type OrRedirect (line 56) | type OrRedirect<T> = T | { redirect: Redirect };
  type RelayOptions (line 58) | interface RelayOptions<
  function defaultVariablesFromContext (line 92) | function defaultVariablesFromContext(
  function defaultQueryOptionsFromContext (line 98) | function defaultQueryOptionsFromContext(
  function useStableIdentity (line 104) | function useStableIdentity<T>(nextValue: T): T {
  function withRelay (line 115) | function withRelay<Props extends RelayProps, ServerSideProps extends {}>(
  function relayInitialProps (line 175) | function relayInitialProps<
  function getServerInitialProps (line 188) | async function getServerInitialProps<
  function getClientInitialProps (line 257) | async function getClientInitialProps<
  function ensureQueryFlushed (line 291) | async function ensureQueryFlushed(

FILE: src/json_meta.ts
  type EncodedType (line 12) | enum EncodedType {
  type HydrationMeta (line 17) | type HydrationMeta = Record<string, EncodedType>;
  function hydrateObject (line 24) | function hydrateObject(meta: HydrationMeta, value: any) {
  function createReplacer (line 73) | function createReplacer() {
  function collectMeta (line 99) | function collectMeta(obj: unknown) {
  function checkExhaustive (line 105) | function checkExhaustive(cased: never): asserts cased is never {

FILE: website/src/pages/index.js
  function Feature (line 46) | function Feature({ imageUrl, title, description }) {
  function Home (line 61) | function Home() {
Condensed preview — 48 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (114K chars).
[
  {
    "path": ".gitignore",
    "chars": 1610,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs."
  },
  {
    "path": ".prettierignore",
    "chars": 174,
    "preview": "node_modules/\ndist/\npackage*.json\n\nexample/.next\nexample/src/queries/__generated__\nexample/data\n\nwebsite/node_modules\nwe"
  },
  {
    "path": ".prettierrc",
    "chars": 104,
    "preview": "{\n  \"singleQuote\": true,\n  \"bracketSpacing\": true,\n  \"arrowParens\": \"always\",\n  \"proseWrap\": \"always\"\n}\n"
  },
  {
    "path": "LICENSE",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2021 Revere CRE\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "README.md",
    "chars": 7373,
    "preview": "<p align=\"center\">\n  <b>Revere CRE is hiring! Interested in working on the cutting edge of frontend?</b>\n  <br>\n  <b>Rea"
  },
  {
    "path": "example/.gitignore",
    "chars": 386,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "example/.prettierignore",
    "chars": 48,
    "preview": ".next\ndata\nnode_modules\n**/__generated__\npublic/"
  },
  {
    "path": "example/.prettierrc",
    "chars": 79,
    "preview": "{\n  \"singleQuote\": true,\n  \"bracketSpacing\": true,\n  \"arrowParens\": \"always\"\n}\n"
  },
  {
    "path": "example/README.md",
    "chars": 1581,
    "preview": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js"
  },
  {
    "path": "example/data/schema.graphql",
    "chars": 35866,
    "preview": "schema {\n  query: Root\n}\n\n\"\"\"A single film.\"\"\"\ntype Film implements Node {\n  \"\"\"The title of this film.\"\"\"\n  title: Stri"
  },
  {
    "path": "example/next-env.d.ts",
    "chars": 201,
    "preview": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edite"
  },
  {
    "path": "example/next.config.js",
    "chars": 359,
    "preview": "const relay = require('./relay.config');\n\nmodule.exports = {\n  compiler: {\n    relay,\n  },\n  webpack: (config, { isServe"
  },
  {
    "path": "example/package.json",
    "chars": 770,
    "preview": "{\n  \"name\": \"example\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next"
  },
  {
    "path": "example/postcss.config.js",
    "chars": 83,
    "preview": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "example/relay.config.js",
    "chars": 207,
    "preview": "module.exports = {\n  src: './src',\n  schema: './data/schema.graphql',\n  exclude: ['**/node_modules/**', '**/__generated_"
  },
  {
    "path": "example/src/lib/relay_client_environment.ts",
    "chars": 867,
    "preview": "import { Environment, Network, Store, RecordSource } from 'relay-runtime';\n\nexport function createClientNetwork() {\n  re"
  },
  {
    "path": "example/src/lib/server/relay_server_environment.ts",
    "chars": 711,
    "preview": "import { Environment, Network, Store, RecordSource } from 'relay-runtime';\n\nexport function createServerNetwork() {\n  re"
  },
  {
    "path": "example/src/pages/_app.tsx",
    "chars": 624,
    "preview": "import { getClientEnvironment } from 'lib/relay_client_environment';\nimport type { AppProps } from 'next/app';\nimport { "
  },
  {
    "path": "example/src/pages/film/[id].tsx",
    "chars": 2079,
    "preview": "import { getClientEnvironment } from 'lib/relay_client_environment';\nimport Link from 'next/link';\nimport type { Id_film"
  },
  {
    "path": "example/src/pages/index.tsx",
    "chars": 1847,
    "preview": "import { getClientEnvironment } from 'lib/relay_client_environment';\nimport type { pages_listFilmsQuery } from 'queries/"
  },
  {
    "path": "example/src/queries/__generated__/Id_filmDescription.graphql.ts",
    "chars": 1149,
    "preview": "/**\n * @generated SignedSource<<ed9ee293b0eda0b05416355b644db88d>>\n * @lightSyntaxTransform\n * @nogrep\n */\n\n/* tslint:di"
  },
  {
    "path": "example/src/queries/__generated__/Id_filmQuery.graphql.ts",
    "chars": 2951,
    "preview": "/**\n * @generated SignedSource<<09ea506460f6041b1df834301726692e>>\n * @lightSyntaxTransform\n * @nogrep\n */\n\n/* tslint:di"
  },
  {
    "path": "example/src/queries/__generated__/pages_listFilmsQuery.graphql.ts",
    "chars": 2439,
    "preview": "/**\n * @generated SignedSource<<4b786e7bb83bbdb4f636d8f71dac9a5f>>\n * @lightSyntaxTransform\n * @nogrep\n */\n\n/* tslint:di"
  },
  {
    "path": "example/tailwind.config.js",
    "chars": 249,
    "preview": "module.exports = {\n  purge: [\n    './src/pages/**/*.{js,ts,jsx,tsx}',\n    './src/components/**/*.{js,ts,jsx,tsx}',\n  ],\n"
  },
  {
    "path": "example/tsconfig.json",
    "chars": 637,
    "preview": "{\n  \"compilerOptions\": {\n    \"allowJs\": false,\n    \"allowUnusedLabels\": false,\n    \"baseUrl\": \"./src\",\n    \"esModuleInte"
  },
  {
    "path": "package.json",
    "chars": 1269,
    "preview": "{\n  \"name\": \"relay-nextjs\",\n  \"version\": \"3.0.1\",\n  \"description\": \"Use Relay in your Next.js apps!\",\n  \"main\": \"index.j"
  },
  {
    "path": "src/app.ts",
    "chars": 2235,
    "preview": "/* eslint-disable @typescript-eslint/no-unnecessary-condition */\nimport { useMemo, useState } from 'react';\nimport type "
  },
  {
    "path": "src/component.tsx",
    "chars": 8534,
    "preview": "import isEqual from 'lodash.isequal';\nimport type { NextPageContext, Redirect } from 'next';\nimport Router, { NextRouter"
  },
  {
    "path": "src/index.tsx",
    "chars": 114,
    "preview": "export { withRelay } from './component';\nexport type { OrRedirect, RelayOptions, RelayProps } from './component';\n"
  },
  {
    "path": "src/json_meta.ts",
    "chars": 3135,
    "preview": "/**\n * @fileoverview Functions for extracting and applying serialization metadata\n * to JSON. When a JS object is serial"
  },
  {
    "path": "tsconfig.json",
    "chars": 557,
    "preview": "{\n  \"compilerOptions\": {\n    \"module\": \"CommonJS\",\n    \"target\": \"ES2015\",\n    \"jsx\": \"react-jsx\",\n    \"outDir\": \"dist\","
  },
  {
    "path": "website/.gitignore",
    "chars": 233,
    "preview": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.lo"
  },
  {
    "path": "website/README.md",
    "chars": 745,
    "preview": "# Website\n\nThis website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.\n\n##"
  },
  {
    "path": "website/babel.config.js",
    "chars": 89,
    "preview": "module.exports = {\n  presets: [require.resolve('@docusaurus/core/lib/babel/preset')],\n};\n"
  },
  {
    "path": "website/docs/app-api.md",
    "chars": 1031,
    "preview": "---\ntitle: Relay App API\n---\n\n`relay-nextjs/app` exposes a single hook to configure your app to use Relay.\n\n## `useRelay"
  },
  {
    "path": "website/docs/configuration.md",
    "chars": 5531,
    "preview": "---\ntitle: Configuring relay-nextjs\n---\n\n## Installing `relay-nextjs`\n\nInstall using npm or your other favorite package "
  },
  {
    "path": "website/docs/installation-and-setup.md",
    "chars": 2911,
    "preview": "---\ntitle: Installation and Setup\n---\n\n## Installing Relay\n\nRelay comes with quite a number of dependencies that don't i"
  },
  {
    "path": "website/docs/lazy-loaded-query.md",
    "chars": 2128,
    "preview": "---\ntitle: Lazy-loaded Queries\n---\n\nRelay's\n[`useLazyLoadQuery` API](https://relay.dev/docs/api-reference/use-lazy-load-"
  },
  {
    "path": "website/docs/page-api.md",
    "chars": 3418,
    "preview": "---\ntitle: Relay Page API\n---\n\n## `withRelay`\n\nWraps a component, GraphQL query, and a set of options to manage loading "
  },
  {
    "path": "website/docs/prerequisites.md",
    "chars": 1282,
    "preview": "---\ntitle: Prerequisites\n---\n\nFirst check out the [Relay docs](https://relay.dev) and the\n[Relay prerequisites](https://"
  },
  {
    "path": "website/docs/what-why.md",
    "chars": 720,
    "preview": "---\ntitle: What is relay-nextjs\nslug: /\n---\n\n`relay-nextjs` is a bridge between [Next.js](https://nextjs.org/) and\n[Rela"
  },
  {
    "path": "website/docusaurus.config.js",
    "chars": 1374,
    "preview": "/** @type {import('@docusaurus/types').DocusaurusConfig} */\nmodule.exports = {\n  title: 'relay-nextjs',\n  tagline: 'Rela"
  },
  {
    "path": "website/package.json",
    "chars": 873,
    "preview": "{\n  \"name\": \"website\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"docusaurus\": \"docusaurus\",\n    \"star"
  },
  {
    "path": "website/sidebars.js",
    "chars": 435,
    "preview": "module.exports = {\n  docs: [\n    {\n      type: 'category',\n      label: 'Getting Started',\n      items: [\n        'what-"
  },
  {
    "path": "website/src/css/custom.css",
    "chars": 826,
    "preview": "/* stylelint-disable docusaurus/copyright-header */\n/**\n * Any CSS included here will be global. The classic template\n *"
  },
  {
    "path": "website/src/pages/index.js",
    "chars": 2961,
    "preview": "import React from 'react';\nimport clsx from 'clsx';\nimport Layout from '@theme/Layout';\nimport Link from '@docusaurus/Li"
  },
  {
    "path": "website/src/pages/styles.module.css",
    "chars": 559,
    "preview": "/* stylelint-disable docusaurus/copyright-header */\n\n/**\n * CSS files with the .module.css suffix will be treated as CSS"
  },
  {
    "path": "website/static/.nojekyll",
    "chars": 0,
    "preview": ""
  }
]

About this extraction

This page contains the full source code of the RevereCRE/relay-nextjs GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 48 files (102.0 KB), approximately 27.1k tokens, and a symbol index with 38 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!