├── pages
├── queryCountries.graphql
├── api
│ └── hello.ts
├── _app.tsx
└── index.tsx
├── public
├── favicon.ico
└── vercel.svg
├── .github
└── dependabot.yml
├── @types
└── graphql.d.ts
├── next-env.d.ts
├── styles
├── Home.module.css
└── globals.css
├── next.config.js
├── hooks
└── useApollo.ts
├── README.md
├── .gitignore
├── package.json
├── tsconfig.json
├── .vscode
└── settings.json
└── apollo
└── index.ts
/pages/queryCountries.graphql:
--------------------------------------------------------------------------------
1 | query {
2 | countries {
3 | code
4 | name
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanms1/nextjs-apollo-ssr/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "monthly"
7 |
--------------------------------------------------------------------------------
/@types/graphql.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.graphql' {
2 | import { DocumentNode } from 'graphql';
3 | const Schema: DocumentNode;
4 |
5 | export = Schema;
6 | }
7 |
--------------------------------------------------------------------------------
/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default (req, res) => {
4 | res.statusCode = 200
5 | res.json({ name: 'John Doe' })
6 | }
7 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | /* This is all we need */
2 | .container {
3 | min-height: 100vh;
4 | padding: 0 0.5rem;
5 | display: flex;
6 | flex-direction: column;
7 | justify-content: center;
8 | align-items: center;
9 | }
10 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | webpack: (config) => {
3 | config.module.rules.push({
4 | test: /\.(graphql|gql)$/,
5 | exclude: /node_modules/,
6 | loader: 'graphql-tag/loader',
7 | });
8 | return config;
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------
/hooks/useApollo.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 |
3 | import { APOLLO_STATE_PROP_NAME, initializeApollo } from '../apollo';
4 |
5 | function useApollo(pageProps: any) {
6 | const state = pageProps[APOLLO_STATE_PROP_NAME];
7 | const client = useMemo(() => initializeApollo(state), [state]);
8 |
9 | return client;
10 | }
11 |
12 | export default useApollo;
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Next js + GraphQL + TypeScript Setup
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | ## Learn to create this template [here](https://dev.to/ivanms1/take-your-next-js-graphql-typescript-setup-to-the-next-level-5b0i)
16 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { AppProps } from 'next/app';
2 | import { ApolloProvider } from '@apollo/client';
3 |
4 | import useApollo from '../hooks/useApollo';
5 |
6 | import '../styles/globals.css';
7 |
8 | function MyApp({ Component, pageProps }: AppProps) {
9 | const client = useApollo(pageProps);
10 | return (
11 |
12 |
13 |
14 | );
15 | }
16 |
17 | export default MyApp;
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-graphql-app",
3 | "version": "0.1.0",
4 | "scripts": {
5 | "dev": "next dev",
6 | "build": "next build",
7 | "start": "next start"
8 | },
9 | "dependencies": {
10 | "@apollo/client": "^3.7.13",
11 | "deepmerge": "^4.3.0",
12 | "graphql": "^16.6.0",
13 | "lodash-es": "^4.17.21",
14 | "next": "12.3.1",
15 | "react": "18.1.0",
16 | "react-dom": "18.1.0"
17 | },
18 | "devDependencies": {
19 | "@types/lodash-es": "^4.17.7",
20 | "@types/node": "^18.11.11",
21 | "@types/react": "^18.0.12",
22 | "typescript": "^5.0.4"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true
21 | },
22 | "include": [
23 | "next-env.d.ts",
24 | "**/*.ts",
25 | "**/*.tsx"
26 | ],
27 | "exclude": [
28 | "node_modules"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.validate": [
3 | "javascript",
4 | "javascriptreact",
5 | "typescript",
6 | "typescriptreact"
7 | ],
8 | "[typescriptreact]": {
9 | "editor.defaultFormatter": "esbenp.prettier-vscode"
10 | },
11 | "[typescript]": {
12 | "editor.defaultFormatter": "esbenp.prettier-vscode"
13 | },
14 | "[json]": {
15 | "editor.defaultFormatter": "esbenp.prettier-vscode"
16 | },
17 | "[jsonc]": {
18 | "editor.defaultFormatter": "esbenp.prettier-vscode"
19 | },
20 | "editor.formatOnSave": true,
21 | "editor.formatOnPaste": true,
22 | "typescript.tsdk": "node_modules/typescript/lib",
23 | "gitlens.codeLens.includeSingleLineSymbols": true,
24 | "typescript.preferences.quoteStyle": "single",
25 | "javascript.preferences.quoteStyle": "single",
26 | "prettier.jsxSingleQuote": true,
27 | "prettier.singleQuote": true
28 | }
29 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import { useQuery } from '@apollo/client';
3 | import type { GetStaticProps } from 'next';
4 |
5 | import QUERY_COUNTRIES from './queryCountries.graphql';
6 |
7 | import { addApolloState, initializeApollo } from '../apollo';
8 |
9 | import styles from '../styles/Home.module.css';
10 |
11 | export default function Home() {
12 | const { data, loading, error } = useQuery(QUERY_COUNTRIES);
13 |
14 | // check for errors
15 | if (error) {
16 | return
:( an error happened
;
17 | }
18 |
19 | // if all good return data
20 | return (
21 |
22 |
23 |
Countries GraphQL
24 |
25 |
26 |
Countries
27 | {/* let the user know we are fetching the countries */}
28 | {loading &&
loading...
}
29 |
30 | {data?.countries?.map((country) => (
31 |
{country.name}
32 | ))}
33 |
34 |
35 | );
36 | }
37 |
38 | export const getStaticProps: GetStaticProps = async (ctx) => {
39 | const client = initializeApollo();
40 |
41 | await client.query({
42 | query: QUERY_COUNTRIES,
43 | });
44 |
45 | return addApolloState(client, {
46 | props: {},
47 | });
48 | };
49 |
--------------------------------------------------------------------------------
/apollo/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ApolloClient,
3 | InMemoryCache,
4 | type NormalizedCacheObject,
5 | } from '@apollo/client';
6 | import merge from 'deepmerge';
7 | import isEqual from 'lodash-es/isEqual';
8 |
9 | const COUNTRIES_API = 'https://countries.trevorblades.com';
10 |
11 | export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';
12 |
13 | let apolloClient: ApolloClient | null;
14 |
15 | function createApolloClient() {
16 | return new ApolloClient({
17 | ssrMode: typeof window === 'undefined',
18 | uri: COUNTRIES_API,
19 | cache: new InMemoryCache(),
20 | });
21 | }
22 |
23 | export function initializeApollo(initialState?: any) {
24 | const _apolloClient = apolloClient ?? createApolloClient();
25 |
26 | if (initialState) {
27 | const existingCache = _apolloClient.cache.extract();
28 |
29 | const data = merge(initialState, existingCache, {
30 | arrayMerge: (destinationArray, sourceArray) => [
31 | ...sourceArray,
32 | ...destinationArray.filter((d) =>
33 | sourceArray.every((s) => !isEqual(d, s))
34 | ),
35 | ],
36 | });
37 | _apolloClient.cache.restore(data);
38 | }
39 |
40 | if (typeof window === 'undefined') {
41 | return _apolloClient;
42 | }
43 |
44 | if (!apolloClient) {
45 | apolloClient = _apolloClient;
46 | }
47 |
48 | return _apolloClient;
49 | }
50 |
51 | export function addApolloState(
52 | client: ApolloClient,
53 | pageProps: any
54 | ) {
55 | if (pageProps?.props) {
56 | pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
57 | }
58 |
59 | return pageProps;
60 | }
61 |
--------------------------------------------------------------------------------