├── 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 | 3 | 4 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------