├── .babelrc ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .graphqlrc ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── __generated__ └── typescript-operations.ts ├── codegen.yaml ├── components ├── Common │ └── MetaData │ │ └── MetaData.tsx └── HomePage │ ├── Carousel │ ├── Carousel.scss │ ├── CarouselHooks.tsx │ └── CarouselRenderProps.tsx │ ├── HomePageFooter │ ├── HomePageFooter.scss │ └── HomePageFooter.tsx │ ├── Subscription │ ├── SUBSCRIBE.graphql │ ├── Subscription.scss │ └── Subscription.tsx │ └── SubscriptionsTable │ ├── SUBSCRIPTIONS.graphql │ ├── SubscriptionsTable.scss │ └── SubscriptionsTable.tsx ├── config └── config.js ├── images ├── ath_logo.png ├── ath_logo_white.png ├── atheros_logo.png ├── atheros_logo_black.png ├── graphql_mastery_logo_small.png ├── graphql_mastery_twitter.png └── graphql_mastery_white_middle.png ├── lib ├── gtag.ts ├── init-apollo.tsx └── with-apollo-client-static.tsx ├── next-env.d.ts ├── next.config.js ├── nodemon.json ├── package-lock.json ├── package.json ├── pages ├── _document.tsx └── index.tsx ├── public ├── browserconfig.xml ├── favicons │ ├── android-chrome-144x144.png │ ├── android-chrome-192x192.png │ ├── android-chrome-256x256.png │ ├── android-chrome-36x36.png │ ├── android-chrome-384x384.png │ ├── android-chrome-48x48.png │ ├── android-chrome-512x512.png │ ├── android-chrome-72x72.png │ ├── android-chrome-96x96.png │ ├── apple-touch-icon-114x114.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-144x144.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-167x167.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-57x57.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-72x72.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon-precomposed.png │ ├── apple-touch-icon.png │ ├── apple-touch-startup-image-1182x2208.png │ ├── apple-touch-startup-image-1242x2148.png │ ├── apple-touch-startup-image-1496x2048.png │ ├── apple-touch-startup-image-1536x2008.png │ ├── apple-touch-startup-image-320x460.png │ ├── apple-touch-startup-image-640x1096.png │ ├── apple-touch-startup-image-640x920.png │ ├── apple-touch-startup-image-748x1024.png │ ├── apple-touch-startup-image-750x1294.png │ ├── apple-touch-startup-image-768x1004.png │ ├── coast-228x228.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── favicon.png │ ├── firefox_app_128x128.png │ ├── firefox_app_512x512.png │ ├── firefox_app_60x60.png │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── mstile-70x70.png │ └── yandex-browser-50x50.png ├── images │ ├── graph_bg.png │ ├── social_fb.png │ └── social_twitter.png ├── manifest.json ├── manifest.webapp ├── robots.txt ├── sitemap.xml └── yandex-browser-manifest.json ├── secrets ├── development-local.env ├── production-local.env ├── production-production.env ├── production-staging.env └── test-local.env ├── server ├── __generated__ │ └── resolver-types.ts ├── config │ └── index.ts ├── index.ts ├── lib │ ├── gen-id.ts │ └── http-redirect.ts ├── requests │ └── subscription-requests.ts └── schema │ ├── resolvers.ts │ └── typeDefs.ts ├── tests ├── global-setup.ts ├── global-teardown.ts ├── integration │ └── example.test.tsx └── jest.config.json ├── theme ├── global.scss └── variables.scss ├── tsconfig.json └── tsconfig.server.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "next/babel" 4 | ] 5 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | editor.formatOnSave = false 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | CUSTOM_ENV=local 3 | PORT=3000 4 | API_URL=http://localhost:3000/graphql 5 | HOST=http://localhost:3000 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | dist 3 | node_modules 4 | __generated__ 5 | server/__generated__ -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "env": { 4 | "jest": true, 5 | "browser": true, 6 | "node": true 7 | }, 8 | "parser": "@typescript-eslint/parser", 9 | "extends": ["airbnb", "plugin:@typescript-eslint/recommended", "plugin:security/recommended"], 10 | "plugins": [ 11 | "security", 12 | "prettier", 13 | "@typescript-eslint" 14 | ], 15 | "settings": { 16 | "import/parsers": { 17 | "@typescript-eslint/parser": [".ts", ".tsx"] 18 | }, 19 | "import/resolver": { 20 | "node": { 21 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 22 | } 23 | } 24 | }, 25 | "rules": { 26 | "react/jsx-filename-extension": [2, { "extensions": [".js", ".jsx", ".ts", ".tsx"] }], 27 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], 28 | "@typescript-eslint/indent": [2, 2], 29 | "jsx-a11y/label-has-associated-control": [ 2, { 30 | "labelAttributes": ["label"], 31 | "controlComponents": ["Field"], 32 | "depth": 3 33 | }] 34 | }, 35 | "overrides": [ 36 | { 37 | "files": ["*.js"], 38 | "rules": { 39 | "@typescript-eslint/no-var-requires": "off" 40 | } 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | 4 | # next.js build output 5 | .next 6 | 7 | # typescript build output 8 | dist 9 | 10 | # exported static files 11 | out -------------------------------------------------------------------------------- /.graphqlrc: -------------------------------------------------------------------------------- 1 | { 2 | "request" : { 3 | "url": "http://localhost:3000/graphql" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true 4 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[javascript]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "editor.formatOnSave": false 5 | }, 6 | "[javascriptreact]": { 7 | "editor.defaultFormatter": "esbenp.prettier-vscode", 8 | "editor.formatOnSave": false 9 | }, 10 | "[typescript]": { 11 | "editor.defaultFormatter": "esbenp.prettier-vscode", 12 | "editor.formatOnSave": false 13 | }, 14 | "[typescriptreact]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode", 16 | "editor.formatOnSave": false 17 | }, 18 | "eslint.autoFixOnSave": true, 19 | "eslint.validate": [ 20 | "javascript", 21 | "javascriptreact", 22 | { 23 | "autoFix": true, 24 | "language": "typescript" 25 | }, 26 | { 27 | "autoFix": true, 28 | "language": "typescriptreact" 29 | } 30 | ], 31 | "prettier-eslint.eslintIntegration": true, 32 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Atheros Intelligence Ltd. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # High performance Next + React + GraphQL starter kit 2 | 3 | The purpose of this starter kit is not to be complete solution, but introduction for creating high performance websites with Next.js, React and GraphQL. We use this repository for new projects at [Atheros Intelligence](https://atheros.ai/) and as the repository for our articles at [GraphQL Mastery](https://atheros.ai/blog) 4 | 5 | * Clone the repository with `git clone git@github.com:atherosai/next-react-graphql-apollo-hooks.git` 6 | * To preserve secure dependencies in `package-lock.json` use `npm ci` to install packages 7 | 8 | ## Node.js version 9 | 10 | Even though that the starter kit should work with older `Node` versions, I would suggest to use latest Node `LTS version`. In `package.json`. We have set requirements as follows: 11 | 12 | ```json 13 | "engines": { 14 | "node": ">=10.0.0", 15 | "npm": ">6" 16 | }, 17 | ``` 18 | 19 | ## Technologies & main features 20 | 21 | * Next.js 22 | * React 23 | * GraphQL (Apollo server) 24 | * Apollo client 25 | * React Apollo Hooks 26 | * Node.js 27 | * TypeScript 28 | * GraphQL Code Generator 29 | * Jest 30 | 31 | ## Production usage 32 | 33 | In order to achieve the best performance you should have enabled http/2 and also enable compression in your reverse proxy (nginx). Up to date Node.js server is also very benefitial. 34 | 35 | ## Environment configuration 36 | 37 | The solution for environment variables is built using [dotenv](https://github.com/motdotla/dotenv) library and two environment variables. Well known `NODE_ENV` variable can be set as `development` or `production` and our `CUSTOM_ENV`, which defines the environment. This can be your `staging`, `production`, `local` environment or even your build server. These two variables define the name of `.env` file in `/secrets` folder that will be used. If we would like to for example define the config for our staging environment we would create the file called `/secrets/production-staging.env` and place all the environment variables there. The example for such a file can be for example the following that we can use for development 38 | 39 | ```bash 40 | NODE_ENV=development 41 | CUSTOM_ENV=local 42 | PORT=3000 43 | API_URL=http://localhost:3000/graphql 44 | HOST=http://localhost:3000 45 | ``` 46 | 47 | ## Security and audit 48 | 49 | You can run security audit on dependencies with. Be sure that you use `package-lock.json` in our repository. 50 | 51 | `npm audit` 52 | -------------------------------------------------------------------------------- /__generated__/typescript-operations.ts: -------------------------------------------------------------------------------- 1 | export type Maybe = T | null; 2 | export type SubscribeMutationVariables = { 3 | input: SubscribeInput 4 | }; 5 | 6 | 7 | export type SubscribeMutation = ( 8 | { __typename?: 'Mutation' } 9 | & { subscribe: ( 10 | { __typename?: 'Subscription' } 11 | & Pick 12 | ) } 13 | ); 14 | 15 | export type SubscriptionsQueryVariables = {}; 16 | 17 | 18 | export type SubscriptionsQuery = ( 19 | { __typename?: 'Query' } 20 | & { subscriptions: Maybe 23 | )>>> } 24 | ); 25 | 26 | /** All built-in and custom scalars, mapped to their actual values */ 27 | export type Scalars = { 28 | ID: string, 29 | String: string, 30 | Boolean: boolean, 31 | Int: number, 32 | Float: number, 33 | }; 34 | 35 | export type Mutation = { 36 | __typename?: 'Mutation', 37 | subscribe: Subscription, 38 | }; 39 | 40 | 41 | export type MutationSubscribeArgs = { 42 | input: SubscribeInput 43 | }; 44 | 45 | export type Query = { 46 | __typename?: 'Query', 47 | subscriptions?: Maybe>>, 48 | }; 49 | 50 | export enum SourceEnum { 51 | Article = 'ARTICLE', 52 | HomePage = 'HOME_PAGE' 53 | } 54 | 55 | export type SubscribeInput = { 56 | email: Scalars['String'], 57 | source: SourceEnum, 58 | }; 59 | 60 | export type Subscription = { 61 | __typename?: 'Subscription', 62 | id: Scalars['ID'], 63 | email: Scalars['String'], 64 | source: SourceEnum, 65 | }; 66 | -------------------------------------------------------------------------------- /codegen.yaml: -------------------------------------------------------------------------------- 1 | schema: 'server/schema/typeDefs.ts' 2 | documents: 'components/**/*.graphql' 3 | generates: 4 | __generated__/typescript-operations.ts: 5 | - typescript-operations 6 | - typescript 7 | server/__generated__/resolver-types.ts: 8 | - typescript 9 | - typescript-resolvers 10 | -------------------------------------------------------------------------------- /components/Common/MetaData/MetaData.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GA_TRACKING_ID, CUSTOM_ENV } from '../../../config/config'; 3 | 4 | 5 | const MetaData: React.FunctionComponent = () => ( 6 | <> 7 | 8 | 9 | {CUSTOM_ENV === 'staging' && } 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 87 | 88 | 89 |