├── .gitignore
├── src
├── index.ts
└── withApollo.tsx
├── .travis.yml
├── rollup.config.ts
├── LICENSE
├── tsconfig.json
├── package.json
├── README.md
└── CHANGELOG.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | *.orig
4 | .nvmrc
5 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import withApollo from "./withApollo";
2 |
3 | export { withApollo };
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | sudo: true
3 | dist: trusty
4 | node_js:
5 | - "10"
6 | cache:
7 | directories:
8 | - node_modules
9 | install: npm install
10 |
--------------------------------------------------------------------------------
/rollup.config.ts:
--------------------------------------------------------------------------------
1 | import commonjs from "@rollup/plugin-commonjs";
2 | import resolve from "@rollup/plugin-node-resolve";
3 | import peerDepsExternal from "rollup-plugin-peer-deps-external";
4 | import { terser } from "rollup-plugin-terser";
5 | import typescript from "rollup-plugin-typescript2";
6 | import pkg from "./package.json";
7 |
8 | export default {
9 | input: "src/index.ts",
10 | output: [
11 | {
12 | file: pkg.main,
13 | format: "cjs",
14 | sourcemap: true,
15 | },
16 | {
17 | file: pkg.module,
18 | format: "es",
19 | sourcemap: true,
20 | },
21 | ],
22 | inlineDynamicImports: true,
23 | external: ["react"],
24 | plugins: [
25 | peerDepsExternal(),
26 | resolve(),
27 | typescript({
28 | useTsconfigDeclarationDir: true,
29 | clean: true,
30 | }),
31 | commonjs(),
32 | terser(),
33 | ],
34 | };
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Adam Soffer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
4 | "module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
5 | "lib": ["es6", "dom", "es2016", "es2017"],
6 | "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
7 | "outDir": "dist" /* Redirect output structure to the directory. */,
8 | "declaration": true /* Generates corresponding '.d.ts' file. */,
9 | "declarationDir": "dist" /* Place declaration files into directory */,
10 | "sourceMap": true /* Generates corresponding '.map' file. */,
11 | "strict": true /* Enable all strict type-checking options. */,
12 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
13 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
14 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
15 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
16 | },
17 | "include": ["src/**/*"]
18 | }
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-apollo",
3 | "version": "5.0.8",
4 | "description": "React higher-order component for using the Apollo GraphQL client inside Next.js",
5 | "main": "./dist/index.js",
6 | "module": "./dist/index.es.js",
7 | "scripts": {
8 | "build": "rollup -c rollup.config.ts",
9 | "prepublish": "npm run build",
10 | "precommit": "lint-staged"
11 | },
12 | "lint-staged": {
13 | "*.{js,json}": [
14 | "prettier --write --no-semi --single-quote --jsx-bracket-same-line",
15 | "git add"
16 | ]
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/adamsoffer/next-apollo.git"
21 | },
22 | "keywords": [
23 | "nextjs",
24 | "graphql",
25 | "apollo",
26 | "reactjs"
27 | ],
28 | "author": "Adam Soffer",
29 | "license": "MIT",
30 | "bugs": {
31 | "url": "https://github.com/adamsoffer/next-apollo/issues"
32 | },
33 | "homepage": "https://github.com/adamsoffer/next-apollo#readme",
34 | "files": [
35 | "dist"
36 | ],
37 | "peerDependencies": {
38 | "@apollo/client": "^3.0.0",
39 | "graphql": "^14.0.0 || ^15.0.0",
40 | "next": "^9.3.6 || ^10.0.0 || ^11.0.0",
41 | "react": "^16.8.0 || ^17.0.1"
42 | },
43 | "devDependencies": {
44 | "@apollo/client": "^3.2.2",
45 | "@rollup/plugin-commonjs": "^15.1.0",
46 | "@rollup/plugin-node-resolve": "^9.0.0",
47 | "@types/react": "^16.9.51",
48 | "@types/react-dom": "^16.9.8",
49 | "graphql": "^15.3.0",
50 | "husky": "^4.3.0",
51 | "lint-staged": "^10.4.0",
52 | "next": "^9.5.4 || ^10.0.0 || ^11.0.0",
53 | "prettier": "^2.1.2",
54 | "rollup": "^2.29.0",
55 | "rollup-plugin-peer-deps-external": "^2.2.3",
56 | "rollup-plugin-terser": "^7.0.2",
57 | "rollup-plugin-typescript2": "^0.27.3",
58 | "typescript": "^4.0.3"
59 | },
60 | "dependencies": {}
61 | }
62 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Next Apollo [](https://travis-ci.org/adamsoffer/next-apollo)
2 | A package for using Apollo within a Next.js application.
3 |
4 | [Demo](https://next-with-apollo.vercel.app/)
5 |
6 | ## Installation
7 |
8 | This project assumes you have react, react-dom, and next installed. They're specified as peerDependencies.
9 |
10 | ```
11 | npm install --save next-apollo graphql @apollo/client
12 | ```
13 |
14 | ## Documentation
15 |
16 | Create an Apollo Client, pass it into to the `withApollo` higher-order component and export the returned component.
17 |
18 | ```jsx
19 | import { withApollo } from "next-apollo";
20 | import { ApolloClient, InMemoryCache } from "@apollo/client";
21 |
22 | const apolloClient = new ApolloClient({
23 | uri: "https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn",
24 | cache: new InMemoryCache(),
25 | });
26 |
27 | export default withApollo(apolloClient);
28 | ```
29 |
30 | Inside your Next.js page, wrap your component with your exported higher order component.
31 |
32 | ```jsx
33 | import withApollo from "../lib/apollo";
34 |
35 | const Page = (props) =>
Hello World
;
36 |
37 | // Default export is required for Fast Refresh
38 | export default withApollo({ ssr: true })(Page);
39 | ```
40 |
41 | That's it!
42 |
43 | ## How Does It Work?
44 |
45 | Next-apollo integrates Apollo seamlessly with Next by wrapping our pages inside a higher-order component (HOC). Using a HOC pattern we're able to pass down a central store of query result data created by Apollo into our React component hierarchy defined inside each page of our Next application.
46 |
47 | On initial page load, while on the server and inside `getInitialProps`, the Apollo method, `getDataFromTree`, is invoked and returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized.
48 |
49 | ## License
50 |
51 | MIT
52 |
--------------------------------------------------------------------------------
/src/withApollo.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ApolloClient,
3 | ApolloProvider,
4 | NormalizedCacheObject
5 | } from "@apollo/client";
6 | import { NextPage, NextPageContext } from "next";
7 | import App, { AppContext } from "next/app";
8 | import React from "react";
9 |
10 | // On the client, we store the Apollo Client in the following variable.
11 | // This prevents the client from reinitializing between page transitions.
12 | let globalApolloClient: ApolloClient | null = null;
13 |
14 | type WithApolloOptions = {
15 | apolloClient: ApolloClient;
16 | apolloState: NormalizedCacheObject;
17 | };
18 |
19 | type ContextWithApolloOptions = AppContext & {
20 | ctx: { apolloClient: WithApolloOptions["apolloClient"] };
21 | } & NextPageContext &
22 | WithApolloOptions;
23 |
24 | type ApolloClientParam = ApolloClient
25 | | ((ctx?: NextPageContext) => ApolloClient)
26 |
27 | /**
28 | * Installs the Apollo Client on NextPageContext
29 | * or NextAppContext. Useful if you want to use apolloClient
30 | * inside getStaticProps, getStaticPaths or getServerSideProps
31 | * @param {NextPageContext | AppContext} ctx
32 | */
33 | export const initOnContext = (
34 | acp: ApolloClientParam,
35 | ctx: ContextWithApolloOptions
36 | ) => {
37 | const ac = typeof acp === 'function' ? acp(ctx) : acp as ApolloClient;
38 | const inAppContext = Boolean(ctx.ctx);
39 |
40 | // We consider installing `withApollo({ ssr: true })` on global App level
41 | // as antipattern since it disables project wide Automatic Static Optimization.
42 | if (process.env.NODE_ENV === "development") {
43 | if (inAppContext) {
44 | console.warn(
45 | "Warning: You have opted-out of Automatic Static Optimization due to `withApollo` in `pages/_app`.\n" +
46 | "Read more: https://err.sh/next.js/opt-out-auto-static-optimization\n"
47 | );
48 | }
49 | }
50 |
51 | // Initialize ApolloClient if not already done
52 | const apolloClient =
53 | ctx.apolloClient ||
54 | initApolloClient(ac, ctx.apolloState || {}, inAppContext ? ctx.ctx : ctx);
55 |
56 | // We send the Apollo Client as a prop to the component to avoid calling initApollo() twice in the server.
57 | // Otherwise, the component would have to call initApollo() again but this
58 | // time without the context. Once that happens, the following code will make sure we send
59 | // the prop as `null` to the browser.
60 | (apolloClient as ApolloClient & {
61 | toJSON: () => { [key: string]: any } | null;
62 | }).toJSON = () => null;
63 |
64 | // Add apolloClient to NextPageContext & NextAppContext.
65 | // This allows us to consume the apolloClient inside our
66 | // custom `getInitialProps({ apolloClient })`.
67 | ctx.apolloClient = apolloClient;
68 | if (inAppContext) {
69 | ctx.ctx.apolloClient = apolloClient;
70 | }
71 |
72 | return ctx;
73 | };
74 |
75 | /**
76 | * Always creates a new apollo client on the server
77 | * Creates or reuses apollo client in the browser.
78 | * @param {NormalizedCacheObject} initialState
79 | * @param {NextPageContext} ctx
80 | */
81 | const initApolloClient = (
82 | acp: ApolloClientParam,
83 | initialState: NormalizedCacheObject,
84 | ctx: NextPageContext | undefined
85 | ) => {
86 | const apolloClient = typeof acp === 'function' ? acp(ctx) : acp as ApolloClient;
87 |
88 | // Make sure to create a new client for every server-side request so that data
89 | // isn't shared between connections (which would be bad)
90 | if (typeof window === "undefined") {
91 | return createApolloClient(apolloClient, initialState, ctx);
92 | }
93 |
94 | // Reuse client on the client-side
95 | if (!globalApolloClient) {
96 | globalApolloClient = createApolloClient(apolloClient, initialState, ctx);
97 | }
98 |
99 | return globalApolloClient;
100 | };
101 |
102 | /**
103 | * Creates a withApollo HOC
104 | * that provides the apolloContext
105 | * to a next.js Page or AppTree.
106 | * @param {Object} ac
107 | * @param {Boolean} [withApolloOptions.ssr=false]
108 | * @returns {(PageComponent: NextPage
) => ComponentClass
| FunctionComponent
}
109 | */
110 | export default function withApollo