├── .gitignore ├── NOTES.md ├── README.md ├── codegen.yml ├── generated └── index.tsx ├── graphql └── queries.ts ├── lib └── withApollo.tsx ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── characters │ └── [id].tsx └── index.tsx ├── public ├── favicon.ico └── vercel.svg ├── styles ├── Home.module.css └── globals.css ├── tsconfig.json └── yarn.lock /.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 | -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | ## Apollo client 2 | yarn add next-with-apollo @apollo/client graphql lodash 3 | 4 | yarn add --dev typescript @types/react @types/lodash 5 | 6 | 7 | ## Code generator 8 | yarn add -D @graphql-codegen/typescript-react-apollo @graphql-codegen/cli @graphql-codegen/typescript-operations @graphql-codegen/add 9 | 10 | yarn graphql-codegen init -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Connect Next.js with GraphQL API using Apollo Client 3 2 | 3 | YouTube video: https://www.youtube.com/watch?v=4yDrtFUbUzo 4 | ## Features 5 | * Don't have to use getServerSideProps or getStaticProps for SSR 6 | * Typed hooks generated from the GraphQL schema 7 | 8 | ## Bootstrap Next.js project 9 | * Create app with `npx create-next-app` 10 | * Convert to TypeScript project 11 | * Install dependencies 12 | ## Connect to GraphQL API 13 | * Create Apollo HOC with the `next-with-apollo` modules 14 | * How to control client-side and server-side rendering 15 | 16 | ## Generate Type, hooks with graphql-code generator 17 | 18 | ## Let's keep in touch 19 | - [Subscribe on YouTube](https://www.youtube.com/TomDoesTech) 20 | - [Discord](https://discord.gg/4ae2Esm6P7) 21 | - [Twitter](https://twitter.com/tomdoes_tech) 22 | - [TikTok](https://www.tiktok.com/@tomdoestech) 23 | - [Facebook](https://www.facebook.com/tomdoestech) 24 | - [Instagram](https://www.instagram.com/tomdoestech) 25 | 26 | [Buy me a Coffee](https://www.buymeacoffee.com/tomn) 27 | 28 | [Sign up to DigitalOcean 💖](https://m.do.co/c/1b74cb8c56f4) 29 | -------------------------------------------------------------------------------- /codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: "https://rickandmortyapi.com/graphql" 3 | documents: "graphql/*.ts" 4 | 5 | generates: 6 | generated/index.tsx: 7 | 8 | config: 9 | reactApolloVersion: 3 10 | withHooks: true 11 | plugins: 12 | - add: 13 | content: '// THIS IS A GENERATED FILE, use `yarn codegen` to regenerate' 14 | - add: 15 | content: '/* tslint:disable */' 16 | - "typescript" 17 | - "typescript-operations" 18 | - "typescript-react-apollo" 19 | -------------------------------------------------------------------------------- /generated/index.tsx: -------------------------------------------------------------------------------- 1 | // THIS IS A GENERATED FILE, use `yarn codegen` to regenerate 2 | /* tslint:disable */ 3 | import { gql } from '@apollo/client'; 4 | import * as Apollo from '@apollo/client'; 5 | export type Maybe = T | null; 6 | export type Exact = { [K in keyof T]: T[K] }; 7 | export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; 8 | export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; 9 | const defaultOptions = {} 10 | /** All built-in and custom scalars, mapped to their actual values */ 11 | export type Scalars = { 12 | ID: string; 13 | String: string; 14 | Boolean: boolean; 15 | Int: number; 16 | Float: number; 17 | /** The `Upload` scalar type represents a file upload. */ 18 | Upload: any; 19 | }; 20 | 21 | 22 | export enum CacheControlScope { 23 | Public = 'PUBLIC', 24 | Private = 'PRIVATE' 25 | } 26 | 27 | export type Character = { 28 | __typename?: 'Character'; 29 | /** The id of the character. */ 30 | id?: Maybe; 31 | /** The name of the character. */ 32 | name?: Maybe; 33 | /** The status of the character ('Alive', 'Dead' or 'unknown'). */ 34 | status?: Maybe; 35 | /** The species of the character. */ 36 | species?: Maybe; 37 | /** The type or subspecies of the character. */ 38 | type?: Maybe; 39 | /** The gender of the character ('Female', 'Male', 'Genderless' or 'unknown'). */ 40 | gender?: Maybe; 41 | /** The character's origin location */ 42 | origin?: Maybe; 43 | /** The character's last known location */ 44 | location?: Maybe; 45 | /** 46 | * Link to the character's image. 47 | * All images are 300x300px and most are medium shots or portraits since they are intended to be used as avatars. 48 | */ 49 | image?: Maybe; 50 | /** Episodes in which this character appeared. */ 51 | episode: Array>; 52 | /** Time at which the character was created in the database. */ 53 | created?: Maybe; 54 | }; 55 | 56 | export type Characters = { 57 | __typename?: 'Characters'; 58 | info?: Maybe; 59 | results?: Maybe>>; 60 | }; 61 | 62 | export type Episode = { 63 | __typename?: 'Episode'; 64 | /** The id of the episode. */ 65 | id?: Maybe; 66 | /** The name of the episode. */ 67 | name?: Maybe; 68 | /** The air date of the episode. */ 69 | air_date?: Maybe; 70 | /** The code of the episode. */ 71 | episode?: Maybe; 72 | /** List of characters who have been seen in the episode. */ 73 | characters: Array>; 74 | /** Time at which the episode was created in the database. */ 75 | created?: Maybe; 76 | }; 77 | 78 | export type Episodes = { 79 | __typename?: 'Episodes'; 80 | info?: Maybe; 81 | results?: Maybe>>; 82 | }; 83 | 84 | export type FilterCharacter = { 85 | name?: Maybe; 86 | status?: Maybe; 87 | species?: Maybe; 88 | type?: Maybe; 89 | gender?: Maybe; 90 | }; 91 | 92 | export type FilterEpisode = { 93 | name?: Maybe; 94 | episode?: Maybe; 95 | }; 96 | 97 | export type FilterLocation = { 98 | name?: Maybe; 99 | type?: Maybe; 100 | dimension?: Maybe; 101 | }; 102 | 103 | export type Info = { 104 | __typename?: 'Info'; 105 | /** The length of the response. */ 106 | count?: Maybe; 107 | /** The amount of pages. */ 108 | pages?: Maybe; 109 | /** Number of the next page (if it exists) */ 110 | next?: Maybe; 111 | /** Number of the previous page (if it exists) */ 112 | prev?: Maybe; 113 | }; 114 | 115 | export type Location = { 116 | __typename?: 'Location'; 117 | /** The id of the location. */ 118 | id?: Maybe; 119 | /** The name of the location. */ 120 | name?: Maybe; 121 | /** The type of the location. */ 122 | type?: Maybe; 123 | /** The dimension in which the location is located. */ 124 | dimension?: Maybe; 125 | /** List of characters who have been last seen in the location. */ 126 | residents: Array>; 127 | /** Time at which the location was created in the database. */ 128 | created?: Maybe; 129 | }; 130 | 131 | export type Locations = { 132 | __typename?: 'Locations'; 133 | info?: Maybe; 134 | results?: Maybe>>; 135 | }; 136 | 137 | export type Query = { 138 | __typename?: 'Query'; 139 | /** Get a specific character by ID */ 140 | character?: Maybe; 141 | /** Get the list of all characters */ 142 | characters?: Maybe; 143 | /** Get a list of characters selected by ids */ 144 | charactersByIds?: Maybe>>; 145 | /** Get a specific locations by ID */ 146 | location?: Maybe; 147 | /** Get the list of all locations */ 148 | locations?: Maybe; 149 | /** Get a list of locations selected by ids */ 150 | locationsByIds?: Maybe>>; 151 | /** Get a specific episode by ID */ 152 | episode?: Maybe; 153 | /** Get the list of all episodes */ 154 | episodes?: Maybe; 155 | /** Get a list of episodes selected by ids */ 156 | episodesByIds?: Maybe>>; 157 | }; 158 | 159 | 160 | export type QueryCharacterArgs = { 161 | id: Scalars['ID']; 162 | }; 163 | 164 | 165 | export type QueryCharactersArgs = { 166 | page?: Maybe; 167 | filter?: Maybe; 168 | }; 169 | 170 | 171 | export type QueryCharactersByIdsArgs = { 172 | ids: Array; 173 | }; 174 | 175 | 176 | export type QueryLocationArgs = { 177 | id: Scalars['ID']; 178 | }; 179 | 180 | 181 | export type QueryLocationsArgs = { 182 | page?: Maybe; 183 | filter?: Maybe; 184 | }; 185 | 186 | 187 | export type QueryLocationsByIdsArgs = { 188 | ids: Array; 189 | }; 190 | 191 | 192 | export type QueryEpisodeArgs = { 193 | id: Scalars['ID']; 194 | }; 195 | 196 | 197 | export type QueryEpisodesArgs = { 198 | page?: Maybe; 199 | filter?: Maybe; 200 | }; 201 | 202 | 203 | export type QueryEpisodesByIdsArgs = { 204 | ids: Array; 205 | }; 206 | 207 | 208 | export type CharactersQueryVariables = Exact<{ 209 | page?: Maybe; 210 | filter?: Maybe; 211 | }>; 212 | 213 | 214 | export type CharactersQuery = ( 215 | { __typename?: 'Query' } 216 | & { characters?: Maybe<( 217 | { __typename?: 'Characters' } 218 | & { info?: Maybe<( 219 | { __typename?: 'Info' } 220 | & Pick 221 | )>, results?: Maybe 224 | & { episode: Array 227 | )>>, origin?: Maybe<( 228 | { __typename?: 'Location' } 229 | & Pick 230 | )> } 231 | )>>> } 232 | )> } 233 | ); 234 | 235 | export type CharacterQueryVariables = Exact<{ 236 | id: Scalars['ID']; 237 | }>; 238 | 239 | 240 | export type CharacterQuery = ( 241 | { __typename?: 'Query' } 242 | & { character?: Maybe<( 243 | { __typename?: 'Character' } 244 | & Pick 245 | & { origin?: Maybe<( 246 | { __typename?: 'Location' } 247 | & Pick 248 | )> } 249 | )> } 250 | ); 251 | 252 | 253 | export const CharactersDocument = gql` 254 | query characters($page: Int, $filter: FilterCharacter) { 255 | characters(page: $page, filter: $filter) { 256 | info { 257 | count 258 | pages 259 | next 260 | prev 261 | } 262 | results { 263 | id 264 | image 265 | name 266 | gender 267 | species 268 | episode { 269 | id 270 | episode 271 | air_date 272 | } 273 | origin { 274 | dimension 275 | id 276 | } 277 | } 278 | } 279 | } 280 | `; 281 | 282 | /** 283 | * __useCharactersQuery__ 284 | * 285 | * To run a query within a React component, call `useCharactersQuery` and pass it any options that fit your needs. 286 | * When your component renders, `useCharactersQuery` returns an object from Apollo Client that contains loading, error, and data properties 287 | * you can use to render your UI. 288 | * 289 | * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; 290 | * 291 | * @example 292 | * const { data, loading, error } = useCharactersQuery({ 293 | * variables: { 294 | * page: // value for 'page' 295 | * filter: // value for 'filter' 296 | * }, 297 | * }); 298 | */ 299 | export function useCharactersQuery(baseOptions?: Apollo.QueryHookOptions) { 300 | const options = {...defaultOptions, ...baseOptions} 301 | return Apollo.useQuery(CharactersDocument, options); 302 | } 303 | export function useCharactersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { 304 | const options = {...defaultOptions, ...baseOptions} 305 | return Apollo.useLazyQuery(CharactersDocument, options); 306 | } 307 | export type CharactersQueryHookResult = ReturnType; 308 | export type CharactersLazyQueryHookResult = ReturnType; 309 | export type CharactersQueryResult = Apollo.QueryResult; 310 | export const CharacterDocument = gql` 311 | query character($id: ID!) { 312 | character(id: $id) { 313 | id 314 | image 315 | name 316 | gender 317 | species 318 | origin { 319 | dimension 320 | id 321 | } 322 | } 323 | } 324 | `; 325 | 326 | /** 327 | * __useCharacterQuery__ 328 | * 329 | * To run a query within a React component, call `useCharacterQuery` and pass it any options that fit your needs. 330 | * When your component renders, `useCharacterQuery` returns an object from Apollo Client that contains loading, error, and data properties 331 | * you can use to render your UI. 332 | * 333 | * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; 334 | * 335 | * @example 336 | * const { data, loading, error } = useCharacterQuery({ 337 | * variables: { 338 | * id: // value for 'id' 339 | * }, 340 | * }); 341 | */ 342 | export function useCharacterQuery(baseOptions: Apollo.QueryHookOptions) { 343 | const options = {...defaultOptions, ...baseOptions} 344 | return Apollo.useQuery(CharacterDocument, options); 345 | } 346 | export function useCharacterLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { 347 | const options = {...defaultOptions, ...baseOptions} 348 | return Apollo.useLazyQuery(CharacterDocument, options); 349 | } 350 | export type CharacterQueryHookResult = ReturnType; 351 | export type CharacterLazyQueryHookResult = ReturnType; 352 | export type CharacterQueryResult = Apollo.QueryResult; -------------------------------------------------------------------------------- /graphql/queries.ts: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | 3 | export const CHARACTERS_QUERY = gql` 4 | query characters($page: Int, $filter: FilterCharacter) { 5 | characters(page: $page, filter: $filter) { 6 | info { 7 | count 8 | pages 9 | next 10 | prev 11 | } 12 | results { 13 | id 14 | image 15 | name 16 | gender 17 | species 18 | episode { 19 | id 20 | episode 21 | air_date 22 | } 23 | origin { 24 | dimension 25 | id 26 | } 27 | } 28 | } 29 | } 30 | `; 31 | 32 | export const CHARACTER_QUERY = gql` 33 | query character($id: ID!) { 34 | character(id: $id) { 35 | id 36 | image 37 | name 38 | gender 39 | species 40 | origin { 41 | dimension 42 | id 43 | } 44 | } 45 | } 46 | `; 47 | -------------------------------------------------------------------------------- /lib/withApollo.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ApolloClient, 3 | ApolloProvider, 4 | DefaultOptions, 5 | HttpLink, 6 | InMemoryCache, 7 | } from "@apollo/client"; 8 | import { useRouter } from "next/router"; 9 | import nextWithApollo from "next-with-apollo"; 10 | 11 | const withApollo = nextWithApollo( 12 | ({ initialState, headers }) => { 13 | return new ApolloClient({ 14 | ssrMode: typeof window === "undefined", 15 | link: new HttpLink({ 16 | uri: "https://rickandmortyapi.com/graphql", 17 | }), 18 | headers: { 19 | ...(headers as Record), 20 | }, 21 | cache: new InMemoryCache().restore(initialState || {}), 22 | }); 23 | }, 24 | { 25 | render: ({ Page, props }) => { 26 | const router = useRouter(); 27 | return ( 28 | 29 | 30 | 31 | ); 32 | }, 33 | } 34 | ); 35 | 36 | export default withApollo; 37 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | // next.config.js 2 | module.exports = { 3 | images: { 4 | domains: ["rickandmortyapi.com"], 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-with-apollo-tutorial", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "codegen": "graphql-codegen --config codegen.yml" 10 | }, 11 | "dependencies": { 12 | "@apollo/client": "^3.3.18", 13 | "graphql": "^15.5.0", 14 | "lodash": "^4.17.21", 15 | "next": "10.2.0", 16 | "next-with-apollo": "^5.1.1", 17 | "react": "17.0.2", 18 | "react-dom": "17.0.2" 19 | }, 20 | "devDependencies": { 21 | "@graphql-codegen/add": "^2.0.2", 22 | "@graphql-codegen/cli": "^1.21.4", 23 | "@graphql-codegen/typescript-operations": "^1.17.16", 24 | "@graphql-codegen/typescript-react-apollo": "^2.2.4", 25 | "@types/lodash": "^4.14.169", 26 | "@types/react": "^17.0.5", 27 | "typescript": "^4.2.4" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | 3 | function MyApp({ Component, pageProps }) { 4 | return 5 | } 6 | 7 | export default MyApp 8 | -------------------------------------------------------------------------------- /pages/characters/[id].tsx: -------------------------------------------------------------------------------- 1 | import { get } from "lodash"; 2 | import { getDataFromTree } from "@apollo/client/react/ssr"; 3 | import withApollo from "../../lib/withApollo"; 4 | import { useCharacterQuery } from "../../generated"; 5 | 6 | function SingleCharacterPage({ query }) { 7 | const id = get(query, "id"); 8 | 9 | const { data } = useCharacterQuery({ 10 | variables: { 11 | id, 12 | }, 13 | }); 14 | 15 | return
{JSON.stringify(data)}
; 16 | } 17 | 18 | export default withApollo(SingleCharacterPage, { getDataFromTree }); 19 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { get } from "lodash"; 3 | import Link from "next/link"; 4 | import { getDataFromTree } from "@apollo/client/react/ssr"; 5 | import withApollo from "../lib/withApollo"; 6 | import { CharactersQuery, useCharactersQuery } from "../generated"; 7 | 8 | function Home() { 9 | const { data } = useCharactersQuery(); 10 | 11 | const characters = get( 12 | data, 13 | "characters.results", 14 | [] 15 | ) as CharactersQuery["characters"]["results"]; 16 | 17 | return ( 18 |
19 | {characters.map((character) => ( 20 |
21 | {character.name} 27 | 28 | {character.name} 29 | 30 |
31 | ))} 32 |
33 | ); 34 | } 35 | 36 | export default withApollo(Home, { getDataFromTree }); 37 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TomDoesTech/NextJS-With-Apollo-Client-Tutorial/d0d5f7a9922f2391818477213fe6ca063a48590e/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh; 3 | padding: 0 0.5rem; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | height: 100vh; 9 | } 10 | 11 | .main { 12 | padding: 5rem 0; 13 | flex: 1; 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | } 19 | 20 | .footer { 21 | width: 100%; 22 | height: 100px; 23 | border-top: 1px solid #eaeaea; 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | } 28 | 29 | .footer a { 30 | display: flex; 31 | justify-content: center; 32 | align-items: center; 33 | flex-grow: 1; 34 | } 35 | 36 | .title a { 37 | color: #0070f3; 38 | text-decoration: none; 39 | } 40 | 41 | .title a:hover, 42 | .title a:focus, 43 | .title a:active { 44 | text-decoration: underline; 45 | } 46 | 47 | .title { 48 | margin: 0; 49 | line-height: 1.15; 50 | font-size: 4rem; 51 | } 52 | 53 | .title, 54 | .description { 55 | text-align: center; 56 | } 57 | 58 | .description { 59 | line-height: 1.5; 60 | font-size: 1.5rem; 61 | } 62 | 63 | .code { 64 | background: #fafafa; 65 | border-radius: 5px; 66 | padding: 0.75rem; 67 | font-size: 1.1rem; 68 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 69 | Bitstream Vera Sans Mono, Courier New, monospace; 70 | } 71 | 72 | .grid { 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | flex-wrap: wrap; 77 | max-width: 800px; 78 | margin-top: 3rem; 79 | } 80 | 81 | .card { 82 | margin: 1rem; 83 | padding: 1.5rem; 84 | text-align: left; 85 | color: inherit; 86 | text-decoration: none; 87 | border: 1px solid #eaeaea; 88 | border-radius: 10px; 89 | transition: color 0.15s ease, border-color 0.15s ease; 90 | width: 45%; 91 | } 92 | 93 | .card:hover, 94 | .card:focus, 95 | .card:active { 96 | color: #0070f3; 97 | border-color: #0070f3; 98 | } 99 | 100 | .card h2 { 101 | margin: 0 0 1rem 0; 102 | font-size: 1.5rem; 103 | } 104 | 105 | .card p { 106 | margin: 0; 107 | font-size: 1.25rem; 108 | line-height: 1.5; 109 | } 110 | 111 | .logo { 112 | height: 1em; 113 | margin-left: 0.5rem; 114 | } 115 | 116 | @media (max-width: 600px) { 117 | .grid { 118 | width: 100%; 119 | flex-direction: column; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | }, 21 | "include": [ 22 | "next-env.d.ts", 23 | "**/*.ts", 24 | "**/*.tsx" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | --------------------------------------------------------------------------------