├── .all-contributorsrc
├── .env.local.example
├── .env.test.example
├── .eslintignore
├── .eslintrc.js
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── config.yml
│ ├── feature_request.md
│ └── question.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── ci.yml
│ └── cypress.yml
├── .gitignore
├── .husky
├── .gitignore
└── commit-msg
├── .npmrc
├── .storybook
├── main.js
└── preview.js
├── .tool-versions
├── .vscode
├── extensions.json
├── launch.json
└── settings.json
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── __tests__
└── setupEnv.ts
├── assets
└── screenshot.png
├── babel.config.js
├── build.gradle.kts
├── commitlint.config.js
├── cypress.json
├── cypress
├── .gitignore
├── README.md
├── fixtures
│ └── .gitkeep
├── integration
│ └── app
│ │ └── index.spec.ts
├── plugins
│ └── index.ts
├── support
│ ├── commands.ts
│ ├── console.ts
│ ├── index.d.ts
│ └── index.ts
└── tsconfig.json
├── docker-compose.yml
├── docker
├── Dockerfile
├── docker-entrypoint.sh
└── process.yml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jest.config.js
├── jest.setup.ts
├── next-env.d.ts
├── next-i18next.config.js
├── next.config.js
├── nginx
└── nginx.conf
├── package.json
├── postcss.config.js
├── prettier.config.js
├── public
├── favicon.ico
├── images
│ └── .gitkeep
└── locales
│ ├── de
│ └── common.json
│ └── en
│ └── common.json
├── scripts
├── copyfiles.js
└── setup.js
├── src
├── components
│ └── page
│ │ ├── Page.tsx
│ │ └── index.ts
├── hooks
│ └── useChangeLanguage.ts
├── layouts
│ └── AppLayout.tsx
├── pages
│ ├── 404.tsx
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── _error.tsx
│ └── index.tsx
├── services
│ └── jokes.ts
├── store.ts
├── styles
│ ├── Scrollbar.scss
│ └── index.scss
└── types
│ └── next.d.ts
├── stylelint.config.js
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.test.json
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "nextjs-template",
3 | "projectOwner": "natterstefan",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": false,
11 | "commitConvention": "eslint",
12 | "contributors": [
13 | {
14 | "login": "barnabasJ",
15 | "name": "Barnabas Jovanovics",
16 | "avatar_url": "https://avatars.githubusercontent.com/u/11669837?v=4",
17 | "profile": "https://github.com/barnabasJ",
18 | "contributions": [
19 | "ideas"
20 | ]
21 | },
22 | {
23 | "login": "natterstefan",
24 | "name": "Stefan Natter",
25 | "avatar_url": "https://avatars.githubusercontent.com/u/1043668?v=4",
26 | "profile": "https://natterstefan.me/",
27 | "contributions": [
28 | "ideas"
29 | ]
30 | }
31 | ],
32 | "contributorsPerLine": 7
33 | }
34 |
--------------------------------------------------------------------------------
/.env.local.example:
--------------------------------------------------------------------------------
1 | # SILENCE IS GOLDEN
--------------------------------------------------------------------------------
/.env.test.example:
--------------------------------------------------------------------------------
1 | # SILENCE IS GOLDEN
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build
2 | gradle
3 | node_modules
4 | tmp
5 | version.ts
6 |
7 | # generated files
8 | /graphql/schema.graphql
9 | /graphql/schema.json
10 | __generated__
11 |
12 | # storybook
13 | storybook-static
14 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'eslint-config-ns-ts',
3 | rules: {
4 | /**
5 | * This rule was disabled because of NextJS' Link API.
6 | *
7 | * @see https://github.com/vercel/next.js/issues/5533#issuecomment-573287110
8 | * @see https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/402#issuecomment-368305051
9 | */
10 | 'jsx-a11y/anchor-is-valid': 0,
11 | },
12 | overrides: [
13 | {
14 | files: [
15 | './src/**/__stories__/*.stories.ts',
16 | './src/**/__stories__/*.stories.tsx',
17 | ],
18 | rules: {
19 | 'global-require': 0,
20 | 'import/no-extraneous-dependencies': 0,
21 | 'max-classes-per-file': 0,
22 | 'no-console': 0,
23 | },
24 | },
25 | /**
26 | * exclude jest plugin in cypress files, because test are written
27 | * differently and `expect` is typed different.
28 | */
29 | {
30 | files: [
31 | './cypress/**/*.ts',
32 | './cypress/**/*.tsx',
33 | './src/**/__cypress__/*.tsx',
34 | ],
35 | extends: ['plugin:cypress/recommended'],
36 | rules: {
37 | 'jest/expect-expect': 0,
38 | 'jest/valid-expect': 0,
39 | 'jest/valid-expect-in-promise': 0,
40 | },
41 | },
42 | ],
43 | }
44 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # Thank you
2 |
3 | github: [natterstefan]
4 | patreon: natterstefan
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report 🐞
3 | about: Something isn't working as expected? Here is the right place to report.
4 | ---
5 |
6 |
13 |
14 | # Bug Report
15 |
16 | ## Relevant information
17 |
18 |
19 |
20 | ### Your Environment
21 |
22 | - Browser: **\_**
23 | - Browser version: **\_**
24 | - OS: **\_**
25 | - Package name: **\_**
26 | - Package version: **\_**
27 |
28 | #### Steps to reproduce
29 |
30 | 1. Step 1
31 | 2. Step 2
32 | 3. Step 3
33 |
34 | #### Observed Results
35 |
36 | - What happened? This could be a description, log output, etc.
37 |
38 | #### Expected Results
39 |
40 | - What did you expect to happen?
41 |
42 | #### Relevant Code (optional)
43 |
44 | ```js
45 | // TODO(you): code here to reproduce the problem
46 | ```
47 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Twitter
4 | url: https://twitter.com/natterstefan
5 | about: Get in touch with me on Twitter
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request 💡
3 | about: Suggest a new idea.
4 | ---
5 |
6 |
13 |
14 | # Feature Request
15 |
16 | Brief explanation of the feature you have in mind.
17 |
18 | ## Basic example
19 |
20 | If you want you can include a basic code example. Omit this section if it's
21 | not applicable.
22 |
23 | ## Motivation
24 |
25 | Why are you suggesting this? What is the use case for it and what is the
26 | expected outcome?
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question 🤔
3 | about: Usage question or discussion.
4 | ---
5 |
6 |
13 |
14 | # Question
15 |
16 | ## Relevant information
17 |
18 | Provide as much useful information as you can.
19 |
20 | ### Your Environment
21 |
22 | - Browser: **\_**
23 | - Browser version: **\_**
24 | - OS: **\_**
25 | - Package name: **\_**
26 | - Package version: **\_**
27 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
7 |
8 | # TASK
9 |
10 | ## What I did
11 |
12 | ## How to test
13 |
14 |
21 |
22 | ## Screenshots
23 |
24 |
28 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | if: "! contains(github.event.head_commit.message, '[skip ci]')"
9 | steps:
10 | - uses: actions/checkout@v2
11 | with:
12 | fetch-depth: 1
13 | - uses: actions/setup-node@v2
14 | with:
15 | node-version: 14
16 | - uses: actions/cache@v2
17 | with:
18 | path: node_modules
19 | key: natterstefan-rtk-query-example-${{ hashFiles('**/yarn.lock') }}
20 | - run: yarn
21 | - run: yarn lint
22 | - run: yarn test
23 |
--------------------------------------------------------------------------------
/.github/workflows/cypress.yml:
--------------------------------------------------------------------------------
1 | name: Cypress
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | if: "! contains(github.event.head_commit.message, '[skip ci]')"
12 | steps:
13 | - uses: actions/checkout@v2
14 | with:
15 | fetch-depth: 1
16 | - uses: actions/setup-node@v2
17 | with:
18 | node-version: 14
19 | - uses: actions/cache@v2
20 | with:
21 | path: node_modules
22 | key: natterstefan-rtk-query-example-${{ hashFiles('**/yarn.lock') }}
23 | - run: yarn
24 | - run: yarn test:cypress:ci
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 | /.pnp
44 | .pnp.js
45 |
46 | # TypeScript v1 declaration files
47 | typings/
48 |
49 | # TypeScript cache
50 | *.tsbuildinfo
51 |
52 | # Optional npm cache directory
53 | .npm
54 |
55 | # Optional eslint and stylelint cache
56 | .eslintcache
57 | .stylelintcache
58 |
59 | # Optional jest cache
60 | .jest-cache
61 |
62 | # Microbundle cache
63 | .rpt2_cache/
64 | .rts2_cache_cjs/
65 | .rts2_cache_es/
66 | .rts2_cache_umd/
67 |
68 | # Optional REPL history
69 | .node_repl_history
70 |
71 | # Output of 'npm pack'
72 | *.tgz
73 |
74 | # Yarn Integrity file
75 | .yarn-integrity
76 |
77 | # dotenv environment variables file
78 | .env
79 | .env.test
80 | .env.local
81 | .env.development.local
82 | .env.test.local
83 | .env.production.local
84 |
85 | # parcel-bundler cache (https://parceljs.org/)
86 | .cache
87 |
88 | # Next.js build output
89 | .next
90 | /out/
91 |
92 | # Nuxt.js build / generate output
93 | .nuxt
94 | dist
95 |
96 | # Gatsby files
97 | .cache/
98 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
99 | # https://nextjs.org/blog/next-9-1#public-directory-support
100 | # public
101 |
102 | # vuepress build output
103 | .vuepress/dist
104 |
105 | # Serverless directories
106 | .serverless/
107 |
108 | # FuseBox cache
109 | .fusebox/
110 |
111 | # DynamoDB Local files
112 | .dynamodb/
113 |
114 | # TernJS port file
115 | .tern-port
116 |
117 | # vercel
118 | .vercel
119 |
120 | # production
121 | /build
122 |
123 | # misc
124 | .DS_Store
125 | *.pem
126 | tmp
127 |
128 | # build-files
129 | /version.js
130 | /version.ts
131 |
132 | # Gradle
133 | .gradle
134 |
135 | # storybook
136 | storybook-static
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn commitlint --edit "$1"
5 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | # Enable when you fork this repository and use it for your apps.
2 | # save-exact=true
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | /**
5 | * Storybook uses Webpack@4 by default, but we require Webpack@5 already.
6 | * @see https://storybook.js.org/blog/storybook-for-webpack-5/
7 | * @see https://gist.github.com/shilman/8856ea1786dcd247139b47b270912324#upgrade
8 | */
9 | core: {
10 | builder: 'webpack5',
11 | },
12 | stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
13 | addons: [
14 | '@storybook/addon-links',
15 | '@storybook/addon-essentials',
16 | {
17 | /**
18 | * NOTE: fix Storybook issue with PostCSS@8
19 | * @see https://github.com/storybookjs/storybook/issues/12668#issuecomment-773958085
20 | */
21 | name: '@storybook/addon-postcss',
22 | options: {
23 | postcssLoaderOptions: {
24 | implementation: require('postcss'),
25 | },
26 | },
27 | },
28 | ],
29 | webpackFinal: config => {
30 | /**
31 | * Add *.scss support
32 | * @see https://nebulab.it/blog/nextjs-tailwind-storybook/
33 | */
34 | config.module.rules.push({
35 | test: /\.scss$/,
36 | use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
37 | })
38 |
39 | /**
40 | * Add support for alias-imports
41 | * @see https://github.com/storybookjs/storybook/issues/11989#issuecomment-715524391
42 | */
43 | config.resolve.alias = {
44 | ...config.resolve?.alias,
45 | '@': [path.resolve(__dirname, '../src/'), path.resolve(__dirname, '../')],
46 | }
47 | return config
48 | },
49 | }
50 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | import { withNextRouter } from 'storybook-addon-next-router'
2 |
3 | import '../src/styles/index.scss'
4 |
5 | // @see https://www.npmjs.com/package/storybook-addon-next-router
6 | export const decorators = [
7 | withNextRouter({
8 | path: '/', // defaults to `/`
9 | asPath: '/', // defaults to `/`
10 | query: {}, // defaults to `{}`
11 | push() {}, // defaults to using addon actions integration, can override any method in the router
12 | }),
13 | ]
14 |
15 | export const parameters = {
16 | actions: { argTypesRegex: '^on[A-Z].*' },
17 | controls: {
18 | matchers: {
19 | color: /(background|color)$/i,
20 | date: /Date$/,
21 | },
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | nodejs 14.15.4
2 | yarn 1.22.10
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "apollographql.vscode-apollo",
4 | "bradlc.vscode-tailwindcss",
5 | "dbaeumer.vscode-eslint",
6 | "eamodio.gitlens",
7 | "esbenp.prettier-vscode",
8 | "firsttris.vscode-jest-runner",
9 | "heybourn.headwind",
10 | "johnpapa.vscode-peacock",
11 | "pflannery.vscode-versionlens",
12 | "stylelint.vscode-stylelint",
13 | "syler.sass-indented",
14 | "wayou.vscode-todo-highlight",
15 | "ziyasal.vscode-open-in-github",
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "type": "node",
6 | "request": "launch",
7 | "name": "Jest: Test Current File",
8 | "program": "${workspaceFolder}/node_modules/.bin/jest",
9 | "args": ["${file}", "--detectOpenHandles"],
10 | "console": "integratedTerminal",
11 | "internalConsoleOptions": "neverOpen",
12 | "disableOptimisticBPs": true,
13 | "windows": {
14 | "program": "${workspaceFolder}/node_modules/jest/bin/jest"
15 | }
16 | },
17 | {
18 | "type": "node",
19 | "request": "launch",
20 | "name": "Jest: Test Current File in Watch mode",
21 | "program": "${workspaceFolder}/node_modules/.bin/jest",
22 | "args": ["${file}", "--detectOpenHandles", "--watch"],
23 | "console": "integratedTerminal",
24 | "internalConsoleOptions": "neverOpen",
25 | "disableOptimisticBPs": true,
26 | "windows": {
27 | "program": "${workspaceFolder}/node_modules/jest/bin/jest"
28 | }
29 | },
30 | {
31 | "type": "node",
32 | "request": "launch",
33 | "name": "Jest: Run all Tests",
34 | "program": "${workspaceFolder}/node_modules/.bin/jest",
35 | "args": ["--runInBand", "--detectOpenHandles"],
36 | "console": "integratedTerminal",
37 | "internalConsoleOptions": "neverOpen",
38 | "disableOptimisticBPs": true,
39 | "windows": {
40 | "program": "${workspaceFolder}/node_modules/jest/bin/jest"
41 | }
42 | },
43 | {
44 | "type": "node",
45 | "request": "launch",
46 | "name": "Jest: Debug all Tests",
47 | "runtimeArgs": [
48 | "--inspect-brk",
49 | "${workspaceRoot}/node_modules/.bin/jest",
50 | "--runInBand",
51 | "--detectOpenHandles"
52 | ],
53 | "console": "integratedTerminal",
54 | "internalConsoleOptions": "neverOpen",
55 | "port": 9229
56 | },
57 | {
58 | "type": "node",
59 | "request": "attach",
60 | "name": "Attach by Process ID",
61 | "processId": "${command:PickProcess}"
62 | }
63 | ]
64 | }
65 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | * Disable CSS validation in VS Code, to allow @tailwind import and the @apply
4 | * syntax in VS Code.
5 | *
6 | * Enable stylelint to ensure CSS is still validated though.
7 | *
8 | * Docs:
9 | * - @see https://stackoverflow.com/a/62093553/1238150
10 | * - @see https://github.com/microsoft/vscode/issues/103163#issuecomment-672272020
11 | * - @see https://www.meidev.co/blog/visual-studio-code-css-linting-with-tailwind/
12 | */
13 | "css.validate": false,
14 | "less.validate": false,
15 | "scss.validate": false,
16 | "stylelint.enable": true,
17 | /**
18 | * LINTING & FORMATTING
19 | */
20 | "editor.formatOnSave": true,
21 | "editor.codeActionsOnSave": {
22 | "source.fixAll.eslint": true,
23 | },
24 | "eslint.validate": [
25 | "graphql",
26 | "javascript",
27 | "javascriptreact",
28 | "typescript",
29 | "typescriptreact"
30 | ],
31 | "eslint.options": {
32 | "extentions": [
33 | ".d.ts",
34 | ".gql",
35 | ".graphql",
36 | ".js",
37 | ".jsx",
38 | ".ts",
39 | ".tsx"
40 | ]
41 | },
42 | /**
43 | * EDITOR UX
44 | */
45 | "editor.rulers": [
46 | 80,
47 | ],
48 | "editor.tabSize": 2,
49 | "editor.insertSpaces": true,
50 | "files.eol": "\n",
51 | /**
52 | * PLUGINS SETUP
53 | */
54 | "todohighlight.keywords": [
55 | // inspired by: https://github.com/wayou/vscode-todo-highlight/issues/93#issuecomment-390368341
56 | {
57 | "text": "NOTE:",
58 | "color": "#ecf0f1",
59 | "border": "1px solid #2980b9",
60 | "borderRadius": "4px",
61 | "backgroundColor": "#3498db",
62 | },
63 | {
64 | "text": "HACK:",
65 | "color": "#ecf0f1",
66 | "border": "1px solid #8e44ad",
67 | "borderRadius": "4px",
68 | "backgroundColor": "#9b59b6",
69 | },
70 | {
71 | "text": "XXX",
72 | "color": "#ecf0f1",
73 | "border": "1px solid #f39c12",
74 | "borderRadius": "4px",
75 | "backgroundColor": "#f1c40f",
76 | },
77 | {
78 | "text": "FIXME:",
79 | "color": "#ecf0f1",
80 | "border": "1px solid #f39c12",
81 | "borderRadius": "4px",
82 | "backgroundColor": "#f1c40f",
83 | },
84 | {
85 | "text": "ATTENTION:",
86 | "color": "#ecf0f1",
87 | "border": "1px solid #c0392b",
88 | "borderRadius": "4px",
89 | "backgroundColor": "#e74c3c",
90 | },
91 | {
92 | "text": "BUG:",
93 | "color": "#ecf0f1",
94 | "border": "1px solid #c0392b",
95 | "borderRadius": "4px",
96 | "backgroundColor": "#e74c3c",
97 | }
98 | ],
99 | /**
100 | * TYPESCRIPT SETTINGS
101 | */
102 | "typescript.implementationsCodeLens.enabled": true,
103 | "typescript.referencesCodeLens.enabled": true,
104 | "typescript.tsdk": "./node_modules/typescript/lib",
105 | /**
106 | * Peacock for Visual Studio Code
107 | *
108 | * Change the color of the workspace to quickly identify template repositories.
109 | * @see https://www.peacockcode.dev/guide/#overview
110 | */
111 | "workbench.colorCustomizations": {
112 | "activityBar.activeBackground": "#2f7c47",
113 | "activityBar.activeBorder": "#422c74",
114 | "activityBar.background": "#2f7c47",
115 | "activityBar.foreground": "#e7e7e7",
116 | "activityBar.inactiveForeground": "#e7e7e799",
117 | "activityBarBadge.background": "#422c74",
118 | "activityBarBadge.foreground": "#e7e7e7",
119 | "statusBar.background": "#215732",
120 | "statusBar.foreground": "#e7e7e7",
121 | "statusBarItem.hoverBackground": "#2f7c47",
122 | "titleBar.activeBackground": "#215732",
123 | "titleBar.activeForeground": "#e7e7e7",
124 | "titleBar.inactiveBackground": "#21573299",
125 | "titleBar.inactiveForeground": "#e7e7e799"
126 | },
127 | "peacock.color": "#215732",
128 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | https://natterstefan.me.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Stefan Natter
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 | # nextjs-rtk-query-example
2 |
3 | [](https://github.com/natterstefan/nextjs-rtk-query-example/actions/workflows/ci.yml)
4 | [](https://github.com/natterstefan/nextjs-rtk-query-example/issues)
5 | [](https://github.com/natterstefan/nextjs-rtk-query-example/stargazers)
6 | [](https://github.com/natterstefan/nextjs-rtk-query-example/blob/main/LICENSE)
7 | [](https://github.com/natterstefan/nextjs-rtk-query-example)
8 |
9 | ](./assets/screenshot.png)
10 |
11 | Next.js with rtk-query example.
12 |
13 | ## Installation & Setup
14 |
15 | ```bash
16 | yarn
17 | ```
18 |
19 | Once the task is completed you can start the app with `yarn dev`.
20 |
21 | ## Start
22 |
23 | ```bash
24 | # development mode
25 | yarn dev
26 |
27 | # production mode
28 | yarn build
29 | yarn start
30 | ```
31 |
32 | ## Development
33 |
34 | ### Tests & Code Style
35 |
36 | ```bash
37 | yarn test
38 | yarn lint
39 | ```
40 |
41 | ## Deployment
42 |
43 | ### Deploy your own
44 |
45 | Deploy `nextjs-rtk-query-example` using [Vercel](https://vercel.com):
46 |
47 | [](https://vercel.com/import/project?template=https://github.com/natterstefan/nextjs-rtk-query-example)
48 |
49 | ### Docker
50 |
51 | `nextjs-rtk-query-example` also provides a Docker Image (see [docker](./docker)) and
52 | docker-compose setup.
53 |
54 | #### Build Docker Image
55 |
56 | ```bash
57 | ./gradlew buildDockerImage
58 | ```
59 |
60 | #### Run Docker Image
61 |
62 | ```bash
63 | ./gradlew runDockerBuild
64 | ```
65 |
66 | Or when you want to run it in the background:
67 |
68 | ```bash
69 | docker-compose --env-file ./.env.local up -d
70 | ```
71 |
72 | ## License
73 |
74 | [MIT](./LICENSE)
75 |
76 | ## Contributors ✨
77 |
78 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
79 |
80 |
81 |
82 |
83 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
96 |
97 | ---
98 |
99 | _Based on the template._
100 |
--------------------------------------------------------------------------------
/__tests__/setupEnv.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | /**
3 | * FIX: '.env.test' being loaded properly
4 | * @see https://github.com/vercel/next.js/issues/17903#issuecomment-733994841
5 | */
6 |
7 | import { resolve } from 'path'
8 |
9 | import { loadEnvConfig } from '@next/env'
10 |
11 | export default async () => {
12 | const pwd = process.env.PWD || resolve(__dirname, '..')
13 | loadEnvConfig(pwd)
14 | }
15 |
--------------------------------------------------------------------------------
/assets/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natterstefan/nextjs-rtk-query-example/8bd367f7057279f6aa56bd078583db1c7da24183/assets/screenshot.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['next/babel'],
3 | }
4 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | base
3 | id("com.lovelysystems.gradle") version ("1.3.2")
4 | }
5 |
6 | lovely {
7 | gitProject()
8 | dockerProject("natterstefan/nextjs-template")
9 | }
10 |
11 | subprojects {
12 | apply {
13 | plugin("com.lovelysystems.gradle")
14 | }
15 | }
16 |
17 | val writeVersion by tasks.creating {
18 | val versionFile = file("version.js")
19 | versionFile.writeText("module.exports = '${project.version.toString()}';")
20 | outputs.file(versionFile)
21 | }
22 |
23 | val runDockerBuild by tasks.creating {
24 | group = "Development"
25 | description = "Run Docker Image"
26 | dependsOn("buildDockerImage")
27 | doLast {
28 | exec {
29 | commandLine(
30 | "docker-compose",
31 | "--env-file",
32 | ".env.local",
33 | "-f",
34 | "docker-compose.yml",
35 | "up"
36 | )
37 | }
38 | }
39 | }
40 |
41 | val dev by tasks.creating {
42 | group = "Development"
43 | description = "Initialize Repository"
44 | dependsOn("writeVersion")
45 | doLast {
46 | exec {
47 | commandLine(
48 | "yarn"
49 | )
50 | }
51 | }
52 | }
53 |
54 | with(lovely.dockerFiles) {
55 | // and copy all the output into the docker build folder
56 | from(tasks["writeVersion"].outputs)
57 |
58 | // packages
59 | from(".") {
60 | // files required to properly build and run the app in the docker image
61 | include(
62 | "public/**",
63 | "src/**",
64 | ".npmrc",
65 | "babel.config.js",
66 | "next-i18next.config.js",
67 | "next.config.js",
68 | "package.json",
69 | "postcss.config.js",
70 | "tailwind.config.js",
71 | "tsconfig.json",
72 | "yarn.lock"
73 | )
74 |
75 | // exclude any config, test or generated files
76 | exclude(
77 | "**/__generated__",
78 | "**/*.log",
79 | "**/*.md",
80 | "**/.env",
81 | "**/.env.*",
82 | "**/.eslintcache",
83 | "**/.eslintrc.js",
84 | "**/.prettierignore",
85 | "**/.remarkrc.js",
86 | "**/coverage/**",
87 | "**/jest.config.js",
88 | "**/jest.setup.ts",
89 | "**/node_modules/**",
90 | "**/prettier.config.js",
91 | "**/stats.json",
92 | "**/tmp"
93 | )
94 | }
95 |
96 | // Dockerfile
97 | from("docker")
98 | }
99 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ['@commitlint/config-conventional'] }
2 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:3000"
3 | }
4 |
--------------------------------------------------------------------------------
/cypress/.gitignore:
--------------------------------------------------------------------------------
1 | screenshots
2 | videos
--------------------------------------------------------------------------------
/cypress/README.md:
--------------------------------------------------------------------------------
1 | # Cypress
2 |
3 | ## Links
4 |
5 | - [Testing Your App](https://docs.cypress.io/guides/getting-started/testing-your-app)
6 | - [Best Practices](https://docs.cypress.io/guides/references/best-practices)
7 | - [Cypress - TypeScript Deep Dive](https://basarat.gitbook.io/typescript/intro-1/cypress)
8 | - [IDE Integration](https://docs.cypress.io/guides/tooling/IDE-integration#Visual-Studio-Code)
9 |
--------------------------------------------------------------------------------
/cypress/fixtures/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natterstefan/nextjs-rtk-query-example/8bd367f7057279f6aa56bd078583db1c7da24183/cypress/fixtures/.gitkeep
--------------------------------------------------------------------------------
/cypress/integration/app/index.spec.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Headline', () => {
4 | beforeEach(() => {
5 | cy.visit('http://localhost:3000')
6 | })
7 |
8 | it('should contain a link to the English Homepage', () => {
9 | cy.getBySel('title').contains('Random Programming Jokes')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/cypress/plugins/index.ts:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************************
3 | // This example plugins/index.js can be used to load plugins
4 | //
5 | // You can change the location of this file or turn off loading
6 | // the plugins file with the 'pluginsFile' configuration option.
7 | //
8 | // You can read more here:
9 | // https://on.cypress.io/plugins-guide
10 | // ***********************************************************
11 |
12 | /**
13 | * This function is called when a project is opened or re-opened (e.g. due to
14 | * the project's config changing)
15 | */
16 | const plugin: Cypress.PluginConfig = (_on, config) => {
17 | // `on` is used to hook into various events Cypress emits
18 | // `config` is the resolved Cypress config
19 | return config
20 | }
21 |
22 | export default plugin
23 |
--------------------------------------------------------------------------------
/cypress/support/commands.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add('login', (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
26 |
27 | /**
28 | * inspired by
29 | * - @see https://github.com/cypress-io/cypress-realworld-app/blob/7be40079dd77c8fdb19021ded768b099c1441315/cypress/support/commands.ts#L35-L41
30 | *
31 | * TODO: replace with composition commands https://medium.com/@NicholasBoll/cypress-io-scaling-e2e-testing-with-custom-commands-6b72b902aab
32 | */
33 | Cypress.Commands.add('getBySel', (selector, ...args) => {
34 | return cy.get(`[data-cy=${selector}]`, ...args)
35 | })
36 |
37 | Cypress.Commands.add('getBySelLike', (selector, ...args) => {
38 | return cy.get(`[data-cy*=${selector}]`, ...args)
39 | })
40 |
--------------------------------------------------------------------------------
/cypress/support/console.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Spy on both error and warn console.logs to detect if an error has been
3 | * written to the console
4 | *
5 | * inspired by
6 | * - @see https://stackoverflow.com/a/63794694/1238150
7 | *
8 | * Docs
9 | * - @see https://docs.cypress.io/faq/questions/using-cypress-faq#How-do-I-spy-on-console-log
10 | * - @see https://github.com/cypress-io/cypress-example-recipes/blob/46900c4c5085f8d2c6094cd644ca842b36315de2/examples/stubbing-spying__window/cypress/integration/spy-before-load.js#L4-L13
11 | */
12 | Cypress.on('window:before:load', win => {
13 | cy.spy(win.console, 'error')
14 | cy.spy(win.console, 'warn')
15 | })
16 |
--------------------------------------------------------------------------------
/cypress/support/index.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/naming-convention */
2 | // load type definitions that come with Cypress module
3 | ///
4 |
5 | /**
6 | * TODO: remove and use solution proposed in commands.ts instead
7 | */
8 | declare namespace Cypress {
9 | interface Chainable {
10 | /**
11 | * Select and add product to Cart
12 | * @example
13 | * cy.selectProduct(productName, size , color)
14 | */
15 | /**
16 | * Custom command to select DOM element by data-cy attribute.
17 | * @example cy.getBySel('greeting')
18 | */
19 | getBySel(value: string): Chainable
20 | getBySelLike(value: string): Chainable
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/cypress/support/index.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | import './commands'
17 | import './console'
18 |
--------------------------------------------------------------------------------
/cypress/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es5",
5 | "lib": [
6 | "es5",
7 | "dom"
8 | ],
9 | "types": [
10 | "cypress",
11 | "node"
12 | ],
13 | "isolatedModules": false
14 | },
15 | "include": [
16 | "./**/*.ts",
17 | "./**/*.tsx",
18 | "./**/*.d.ts"
19 | ]
20 | }
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | #
2 | # Next.js Docker Setup with Nginx
3 | #
4 | # inspired by
5 | # @see https://steveholgado.com/nginx-for-nextjs/#adding-pm2-to-our-dockerfile
6 | #
7 | version: "3.8"
8 |
9 | services:
10 | app:
11 | image: natterstefan/nextjs-template:dev
12 | networks:
13 | - nextjs
14 | env_file:
15 | - .env.local
16 | environment:
17 | NODE_ENV: ${NODE_ENV:-production}
18 | nginx:
19 | image: nginx:alpine
20 | volumes:
21 | - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
22 | ports:
23 | - 8080:80
24 | networks:
25 | - nextjs
26 |
27 | networks:
28 | nextjs: {}
29 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | # inspired by https://github.com/vercel/next.js/blob/canary/docs/deployment.md#docker-image
2 | # TODO: deactive https://nextjs.org/docs/api-reference/next.config.js/compression when using nginx
3 | ARG node_version=14.15.4
4 | ARG node_image=node:${node_version}-alpine
5 | ARG pm2_version=4.5.1
6 |
7 | FROM $node_image as devDep
8 | WORKDIR /app/
9 | COPY .npmrc package.json yarn.lock ./
10 | RUN yarn install --frozen-lockfile --no-progress
11 |
12 | FROM $node_image as deps
13 | WORKDIR /app/
14 | COPY .npmrc package.json yarn.lock ./
15 | RUN yarn install --frozen-lockfile --no-progress --production=true
16 |
17 | # STAGE BUILDER
18 | FROM $node_image as builder
19 | # https://nextjs.org/telemetry
20 | ENV NEXT_TELEMETRY_DISABLED=1
21 |
22 | WORKDIR /app/
23 |
24 | # package.json and dependency handling
25 | COPY --from=devDep /app/node_modules ./node_modules
26 | COPY package.json ./
27 |
28 | # files for building the app
29 | COPY babel.config.js tsconfig.json ./
30 | COPY next-i18next.config.js ./
31 | COPY next.config.js postcss.config.js tailwind.config.js ./
32 |
33 | # source code of the app
34 | COPY public ./public/
35 | COPY src ./src/
36 |
37 | # build the bundles (codegen -> ts -> js -> bundled)
38 | RUN yarn build
39 |
40 | # STAGE APP
41 | FROM $node_image
42 | LABEL "maintainer"="natterstefan"
43 |
44 | ENV NEXT_TELEMETRY_DISABLED=1
45 | ENV NODE_ENV=production
46 |
47 | WORKDIR /app/
48 |
49 | # set timezone
50 | RUN apk --no-cache add tzdata
51 | RUN ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
52 |
53 | RUN npm install pm2@${pm2_version} -g
54 | COPY docker-entrypoint.sh process.yml ./
55 |
56 | # copy files
57 | COPY --from=deps /app/node_modules ./node_modules
58 | COPY --from=builder /app/package.json ./
59 | COPY --from=builder /app/next-i18next.config.js ./
60 | COPY --from=builder /app/next.config.js ./
61 | COPY --from=builder /app/public ./public
62 | COPY --from=builder /app/.next ./.next
63 |
64 | RUN addgroup -g 1001 -S nodejs
65 | RUN adduser -S nextjs -u 1001
66 | RUN chown -R nextjs:nodejs /app/.next
67 | USER nextjs
68 |
69 | EXPOSE 3000
70 |
71 | ENTRYPOINT ["./docker-entrypoint.sh"]
--------------------------------------------------------------------------------
/docker/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # @see https://pm2.keymetrics.io/docs/usage/docker-pm2-nodejs/
4 | exec pm2-runtime ./process.yml
--------------------------------------------------------------------------------
/docker/process.yml:
--------------------------------------------------------------------------------
1 | # Docs
2 | # @see https://pm2.keymetrics.io/docs/usage/docker-pm2-nodejs/
3 | #
4 | # ATTENTION: cluster mode does not work
5 | #
6 | # @see https://github.com/vercel/next.js/discussions/10675#discussioncomment-54193
7 | # and discussions above this comment, especially https://nttr.st/3sQDgF8!
8 | #
9 | # NOTE: pm2 start would work, but we can't use it (https://stackoverflow.com/a/60603179/1238150)
10 | apps:
11 | - name: "app"
12 | script: "node_modules/.bin/next"
13 | args: "start"
14 | cwd: "./"
15 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natterstefan/nextjs-rtk-query-example/8bd367f7057279f6aa56bd078583db1c7da24183/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin or MSYS, switch paths to Windows format before running java
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=$((i+1))
158 | done
159 | case $i in
160 | (0) set -- ;;
161 | (1) set -- "$args0" ;;
162 | (2) set -- "$args0" "$args1" ;;
163 | (3) set -- "$args0" "$args1" "$args2" ;;
164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=$(save "$@")
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185 | cd "$(dirname "$0")"
186 | fi
187 |
188 | exec "$JAVACMD" "$@"
189 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'jest-preset-ns/presets/react',
3 | globals: {
4 | // @see https://github.com/natterstefan/jest-preset-ns/issues/4#issuecomment-765416205
5 | 'ts-jest': {
6 | tsconfig: 'tsconfig.test.json',
7 | },
8 | },
9 | globalSetup: '/__tests__/setupEnv.ts',
10 | moduleNameMapper: {
11 | '@/(.*)': ['/$1', '/src/$1'],
12 | },
13 | setupFilesAfterEnv: ['/jest.setup.ts'],
14 | testMatch: ['/**/__tests__/*.test.ts', '/**/__tests__/*.test.tsx'],
15 | }
16 |
--------------------------------------------------------------------------------
/jest.setup.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | /* eslint-disable import/no-extraneous-dependencies */
3 | import 'jest-preset-ns/presets/react/jest-setup.js'
4 |
5 | import { setConfig } from 'next/config'
6 | import { PHASE_DEVELOPMENT_SERVER } from 'next/constants'
7 | import i18n from 'i18next'
8 | import { initReactI18next } from 'react-i18next'
9 | import { NextRouter } from 'next/router'
10 |
11 | // @ts-ignore
12 | import NextConfig from './next.config'
13 |
14 | const { publicRuntimeConfig, serverRuntimeConfig } = NextConfig(
15 | PHASE_DEVELOPMENT_SERVER,
16 | {},
17 | )
18 |
19 | /**
20 | * Enables usage of "publicRuntimeConfig" within tests.
21 | * @see https://github.com/vercel/next.js/issues/4024#issuecomment-386016077
22 | */
23 | setConfig({ publicRuntimeConfig, serverRuntimeConfig })
24 |
25 | /**
26 | * Setup i18n in tests
27 | *
28 | * @see https://github.com/i18next/react-i18next/blob/552ed79036c28f282afe7c6ccb525b82b76e71d5/example/test-jest/src/setupTests.js#L4-L23
29 | * @see https://github.com/isaachinman/next-i18next/issues/377#issuecomment-700516821
30 | */
31 | i18n.use(initReactI18next).init({
32 | lng: 'en',
33 | fallbackLng: 'en',
34 |
35 | // have a common namespace used around the full app
36 | ns: ['translations'],
37 | defaultNS: 'translations',
38 |
39 | // debug: true,
40 |
41 | interpolation: {
42 | escapeValue: false, // not needed for react!!
43 | },
44 |
45 | resources: { en: { translations: {} } },
46 | })
47 |
48 | /**
49 | * mockRouter mocks the initial router state of Next.js
50 | * @see https://github.com/vercel/next.js/issues/7479#issuecomment-752418517
51 | *
52 | * FTR: other solutions like this one did not work:
53 | * @see https://github.com/vercel/next.js/issues/7479#issuecomment-533657701
54 | */
55 | const mockRouter: NextRouter = {
56 | basePath: '/',
57 | pathname: '/',
58 | route: '/',
59 | query: {},
60 | asPath: '/',
61 | push: jest.fn(() => Promise.resolve(true)),
62 | replace: jest.fn(() => Promise.resolve(true)),
63 | reload: jest.fn(() => Promise.resolve(true)),
64 | prefetch: jest.fn(() => Promise.resolve()),
65 | back: jest.fn(() => Promise.resolve(true)),
66 | beforePopState: jest.fn(() => Promise.resolve(true)),
67 | isFallback: false,
68 | isLocaleDomain: false,
69 | isPreview: false,
70 | events: {
71 | on: jest.fn(),
72 | off: jest.fn(),
73 | emit: jest.fn(),
74 | },
75 | isReady: true,
76 | }
77 |
78 | // eslint-disable-next-line @typescript-eslint/no-var-requires
79 | const useRouter = jest.spyOn(require('next/router'), 'useRouter')
80 |
81 | /**
82 | * mockNextUseRouter and mockNextUseRouterOnce must be used in tests where one
83 | * needs to use Router features of Next.js. This can be either done for all
84 | * tests (with `beforeAll`) or on a per test basis.
85 | *
86 | * @see https://github.com/vercel/next.js/issues/7479#issuecomment-587145429
87 | */
88 | export function mockNextUseRouter(props?: Partial) {
89 | useRouter.mockImplementation(() => ({
90 | ...mockRouter,
91 | ...props,
92 | }))
93 | }
94 |
95 | export function mockNextUseRouterOnce(props?: Partial) {
96 | useRouter.mockImplementationOnce(() => ({
97 | ...mockRouter,
98 | ...props,
99 | }))
100 | }
101 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | type NextConfig = {
5 | publicRuntimeConfig: {
6 | NODE_ENV: string
7 | }
8 | serverRuntimeConfig: Record
9 | }
10 |
11 | declare module 'next/config' {
12 | export declare function setConfig(configValue: any): void
13 | export default function getConfig(): NextConfig
14 | }
15 |
--------------------------------------------------------------------------------
/next-i18next.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Docs:
3 | * @see https://github.com/isaachinman/next-i18next
4 | * @see https://nextjs.org/docs/advanced-features/i18n-routing
5 | */
6 | module.exports = {
7 | i18n: {
8 | // ATTENTION: The default locale does not have a prefix.
9 | defaultLocale: 'en',
10 | locales: ['en', 'de'],
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const withPlugins = require('next-compose-plugins')
4 |
5 | const { i18n } = require('./next-i18next.config')
6 |
7 | /**
8 | * // @type {import('next').Config}
9 | * TODO: make type above work
10 | * - @see https://github.com/vercel/next.js/issues/8044#issuecomment-525054841
11 | * - @see https://github.com/vercel/next.js/pull/13429#issuecomment-634621879
12 | * – It should be availabe already: https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/config-shared.ts#L12
13 | */
14 | const nextConfig = {
15 | poweredByHeader: false,
16 | /**
17 | * Opt-in to webpack 5 support
18 | * @see https://github.com/vercel/next.js/issues/21679#issuecomment-771941447
19 | */
20 | future: {
21 | webpack5: true,
22 | },
23 | i18n,
24 | publicRuntimeConfig: {
25 | NODE_ENV: process.env.NODE_ENV,
26 | },
27 | }
28 |
29 | module.exports = withPlugins([], nextConfig)
30 |
--------------------------------------------------------------------------------
/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 | # inspired by https://github.com/steveholgado/nextjs-docker-pm2-nginx/blob/12eacfd745be8007087e2945df82ab99eba5999a/nginx/default.conf
2 | events {}
3 |
4 | http {
5 | # Cache zone
6 | proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:10m inactive=7d use_temp_path=off;
7 |
8 | upstream app {
9 | server app:3000;
10 | }
11 |
12 | server {
13 | listen 80 default_server;
14 |
15 | server_name _;
16 | server_tokens off;
17 |
18 | gzip on;
19 | gzip_proxied any;
20 | gzip_comp_level 4;
21 | gzip_types text/css application/javascript image/svg+xml;
22 |
23 | proxy_http_version 1.1;
24 | proxy_set_header Upgrade $http_upgrade;
25 | proxy_set_header Connection 'upgrade';
26 | proxy_set_header Host $host;
27 | proxy_cache_bypass $http_upgrade;
28 |
29 | # BUILT ASSETS (E.G. JS BUNDLES)
30 | # Browser cache - max cache headers from Next.js as build id in url
31 | # Server cache - valid forever (cleared after cache "inactive" period)
32 | location /_next/static {
33 | proxy_cache STATIC;
34 | proxy_pass http://app;
35 | }
36 |
37 | # STATIC ASSETS (E.G. IMAGES)
38 | # Browser cache - "no-cache" headers from Next.js as no build id in url
39 | # Server cache - refresh regularly in case of changes
40 | location /static {
41 | proxy_cache STATIC;
42 | proxy_ignore_headers Cache-Control;
43 | proxy_cache_valid 60m;
44 | proxy_pass http://app;
45 | }
46 |
47 | # DYNAMIC ASSETS - NO CACHE
48 | location / {
49 | proxy_pass http://app;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-template",
3 | "author": "Stefan Natter ",
4 | "version": "0.1.0",
5 | "license": "MIT",
6 | "private": true,
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/natterstefan/nextjs-template.git"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/natterstefan/nextjs-template/issues"
13 | },
14 | "scripts": {
15 | "build-storybook": "build-storybook",
16 | "build": "next build",
17 | "cypress:open": "cypress open",
18 | "cypress:run": "cypress run",
19 | "dev": "next",
20 | "lint": "yarn lint:ts && yarn lint:css",
21 | "lint:ts": "tsc && eslint --cache \"./**/*.{ts,tsx}\"",
22 | "lint:css": "stylelint --cache './**/*.{ts,tsx,scss}'",
23 | "prepare": "is-ci || husky install",
24 | "postinstall": "node scripts/setup",
25 | "start": "next start",
26 | "storybook": "start-storybook -p 6006",
27 | "test": "jest --passWithNoTests",
28 | "test:cypress": "start-server-and-test dev 3000 cypress:open",
29 | "test:cypress:ci": "start-server-and-test dev 3000 cypress:run"
30 | },
31 | "dependencies": {
32 | "@reduxjs/toolkit": "^1.5.1",
33 | "@rtk-incubator/rtk-query": "^0.3.0",
34 | "date-fns": "^2.21.0",
35 | "next": "^10.1.3",
36 | "next-compose-plugins": "^2.2.1",
37 | "next-i18next": "^8.1.3",
38 | "react": "^16.14.0",
39 | "react-cookie": "^4.0.3",
40 | "react-dom": "^16.14.0",
41 | "react-redux": "^7.2.3"
42 | },
43 | "devDependencies": {
44 | "@babel/core": "^7.13.15",
45 | "@commitlint/cli": "^12.1.1",
46 | "@commitlint/config-conventional": "^12.1.1",
47 | "@storybook/addon-actions": "^6.2.8",
48 | "@storybook/addon-essentials": "^6.2.8",
49 | "@storybook/addon-links": "^6.2.8",
50 | "@storybook/addon-postcss": "^2.0.0",
51 | "@storybook/addons": "^6.2.8",
52 | "@storybook/builder-webpack5": "^6.2.8",
53 | "@storybook/react": "^6.2.8",
54 | "@types/enzyme": "^3.10.8",
55 | "@types/node": "^14.14.37",
56 | "@types/react": "^17.0.3",
57 | "@types/react-dom": "^17.0.3",
58 | "all-contributors-cli": "^6.20.0",
59 | "autoprefixer": "^10.2.5",
60 | "babel-loader": "^8.2.2",
61 | "css-loader": "^5.2.1",
62 | "cypress": "^6.9.1",
63 | "enzyme": "^3.11.0",
64 | "eslint": "^7.24.0",
65 | "eslint-config-airbnb": "^18.2.1",
66 | "eslint-config-ns-ts": "^1.6.1",
67 | "eslint-config-prettier": "^8.2.0",
68 | "eslint-plugin-cypress": "^2.11.2",
69 | "eslint-plugin-import": "^2.22.1",
70 | "eslint-plugin-jest": "^24.3.5",
71 | "eslint-plugin-jsx-a11y": "^6.4.1",
72 | "eslint-plugin-prettier": "^3.3.1",
73 | "eslint-plugin-react": "^7.23.2",
74 | "eslint-plugin-react-hooks": "^4.2.0",
75 | "husky": "^6.0.0",
76 | "is-ci": "^3.0.0",
77 | "jest": "^26.6.3",
78 | "jest-preset-ns": "^0.2.0",
79 | "postcss": "^8.2.10",
80 | "postcss-flexbugs-fixes": "^5.0.2",
81 | "postcss-import": "^14.0.1",
82 | "postcss-loader": "^5.2.0",
83 | "postcss-preset-env": "^6.7.0",
84 | "prettier": "^2.2.1",
85 | "sass": "^1.32.8",
86 | "sass-loader": "^11.0.1",
87 | "start-server-and-test": "^1.12.1",
88 | "storybook-addon-next-router": "^2.0.4",
89 | "style-loader": "^2.0.0",
90 | "stylelint": "^13.12.0",
91 | "stylelint-config-recommended": "^4.0.0",
92 | "tailwindcss": "^2.1.1",
93 | "typescript": "^4.2.4",
94 | "webpack": "^5.33.2"
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | 'postcss-import',
4 | 'tailwindcss',
5 | 'postcss-flexbugs-fixes',
6 | [
7 | 'postcss-preset-env',
8 | {
9 | autoprefixer: {
10 | flexbox: 'no-2009',
11 | },
12 | stage: 3,
13 | features: {
14 | 'custom-properties': false,
15 | },
16 | },
17 | ],
18 | ],
19 | }
20 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | module.exports = require('eslint-config-ns-ts/prettier.config')
3 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natterstefan/nextjs-rtk-query-example/8bd367f7057279f6aa56bd078583db1c7da24183/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natterstefan/nextjs-rtk-query-example/8bd367f7057279f6aa56bd078583db1c7da24183/public/images/.gitkeep
--------------------------------------------------------------------------------
/public/locales/de/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "headline": "Next.js Vorlage",
3 | "home_DE": "Home Deutsch",
4 | "home_EN": "Home Englisch"
5 | }
6 |
--------------------------------------------------------------------------------
/public/locales/en/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "headline": "Next.js Template",
3 | "home_DE": "Home German",
4 | "home_EN": "Home English"
5 | }
6 |
--------------------------------------------------------------------------------
/scripts/copyfiles.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 |
4 | /**
5 | * PREARE .env.* based on the given .env.*.example files
6 | */
7 | const fs = require('fs')
8 | const path = require('path')
9 |
10 | module.exports = () => {
11 | const exampleFiles = [
12 | path.join(__dirname, '../.env.local.example'),
13 | path.join(__dirname, '../.env.test.example'),
14 | ]
15 | const configFiles = [
16 | path.join(__dirname, '../.env.local'),
17 | path.join(__dirname, '../.env.test'),
18 | ]
19 |
20 | configFiles.forEach((configFile, i) => {
21 | if (fs.existsSync(configFile)) {
22 | console.log(`${configFile} exists already.`)
23 | } else {
24 | fs.copyFile(exampleFiles[i], configFile, err => {
25 | if (err) {
26 | throw err
27 | }
28 |
29 | console.log(`${configFile} copied`)
30 | })
31 | }
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/scripts/setup.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-var-requires
2 | const copyfiles = require('./copyfiles')
3 |
4 | copyfiles()
5 |
--------------------------------------------------------------------------------
/src/components/page/Page.tsx:
--------------------------------------------------------------------------------
1 | import React, { FunctionComponent } from 'react'
2 |
3 | const Content: FunctionComponent = ({ children }) => {
4 | return {children}
5 | }
6 |
7 | export function Page({ children }: React.PropsWithChildren) {
8 | return {children}
9 | }
10 |
11 | Page.Content = Content
12 |
--------------------------------------------------------------------------------
/src/components/page/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Page'
2 |
--------------------------------------------------------------------------------
/src/hooks/useChangeLanguage.ts:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router'
2 | import { useEffect } from 'react'
3 | import { useCookies } from 'react-cookie'
4 | import addDays from 'date-fns/addDays'
5 |
6 | /**
7 | * useChangeLocation observes the current `locale` of the page and compares it
8 | * wit the the `defaultLocale`. If they do not match it updates the user's
9 | * cookies which will used by Next.js to determine the user's language.
10 | *
11 | * Docs
12 | * - @see https://nextjs.org/docs/advanced-features/i18n-routing#leveraging-the-next_locale-cookie
13 | * - @see https://www.infoxicator.com/how-i-translated-my-next-js-blog
14 | * - @see https://github.com/vinissimus/next-translate#10-how-to-save-the-user-defined-language
15 | *
16 | * Other Links
17 | * - @see https://dev.to/debosthefirst/how-to-use-cookies-for-persisting-users-in-nextjs-4617
18 | */
19 | export const useChangeLocation = () => {
20 | const [cookie, setCookie] = useCookies(['NEXT_LOCALE'])
21 | const { locale, defaultLocale } = useRouter()
22 |
23 | useEffect(() => {
24 | if (cookie.NEXT_LOCALE !== locale) {
25 | const expires = addDays(new Date(), 100)
26 | setCookie('NEXT_LOCALE', locale, { path: '/', expires })
27 | }
28 | }, [locale, defaultLocale, cookie.NEXT_LOCALE, setCookie])
29 | }
30 |
--------------------------------------------------------------------------------
/src/layouts/AppLayout.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react'
2 |
3 | import { useChangeLocation } from '@/hooks/useChangeLanguage'
4 |
5 | export const AppLayout: FC = ({ children }) => {
6 | useChangeLocation()
7 |
8 | return (
9 |
10 |
11 |
16 | {children}
17 |
18 |
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/src/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { NextPage } from 'next'
3 | import Head from 'next/head'
4 |
5 | import { Page } from '@/components/page'
6 |
7 | const NotFoundPage: NextPage = () => {
8 | return (
9 | <>
10 |
11 | 404 - Not Found
12 |
13 |
14 |
15 |
42 |
43 |
44 | >
45 | )
46 | }
47 |
48 | export default NotFoundPage
49 |
--------------------------------------------------------------------------------
/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react'
2 | import Head from 'next/head'
3 | import { AppProps } from 'next/app'
4 | import { CookiesProvider } from 'react-cookie'
5 | import { appWithTranslation } from 'next-i18next'
6 | import { Provider } from 'react-redux'
7 |
8 | import { store } from '../store'
9 |
10 | import '../styles/index.scss'
11 |
12 | /**
13 | * Next.js Docs
14 | * @see https://nextjs.org/docs/advanced-features/custom-app
15 | *
16 | * Layout pattern is inspired by
17 | * @see https://adamwathan.me/2019/10/17/persistent-layout-patterns-in-nextjs/
18 | */
19 | function App({ Component, pageProps }: AppProps) {
20 | const Layout =
21 | (Component as any).layout ||
22 | (({ children }: { children: ReactNode }) => <>{children}>)
23 |
24 | return (
25 | <>
26 |
27 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | >
40 | )
41 | }
42 |
43 | export default appWithTranslation(App)
44 |
--------------------------------------------------------------------------------
/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Document, { Html, Head, Main, NextScript } from 'next/document'
3 |
4 | /**
5 | * @see https://nextjs.org/docs/advanced-features/custom-document
6 | */
7 | export default class MyDocument extends Document {
8 | render() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | )
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/pages/_error.tsx:
--------------------------------------------------------------------------------
1 | import { ServerResponse } from 'http'
2 |
3 | import React from 'react'
4 | import { NextPage } from 'next'
5 |
6 | import { Page } from '@/components/page'
7 |
8 | type ErrorProps = {
9 | statusCode?: ServerResponse['statusCode'] | null
10 | }
11 |
12 | const ErrorPage: NextPage = ({ statusCode }) => {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
34 |
35 |
36 | Error {statusCode}
37 |
38 |
39 |
An error occurred.
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | ErrorPage.getInitialProps = async ({ res, err }) => {
51 | let statusCode = null
52 |
53 | if (res) {
54 | ;({ statusCode } = res)
55 | } else if (err) {
56 | ;({ statusCode } = err)
57 | }
58 |
59 | const props: ErrorProps = {
60 | statusCode,
61 | }
62 |
63 | return props
64 | }
65 |
66 | export default ErrorPage
67 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-nested-ternary */
2 | import React from 'react'
3 | import { GetServerSideProps } from 'next'
4 | import Head from 'next/head'
5 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
6 |
7 | import { Page } from '@/components/page'
8 | import { AppLayout } from '@/layouts/AppLayout'
9 | import { NextPageType } from '@/types/next'
10 |
11 | import { useGetRandomJokeQuery } from '../services/jokes'
12 |
13 | /**
14 | * What do you call a group of 8 Hobbits?
15 | * A Hobbyte.
16 | */
17 | const IndexPage: NextPageType = () => {
18 | const { data, isFetching, refetch } = useGetRandomJokeQuery(undefined, {
19 | pollingInterval: 10000,
20 | })
21 |
22 | return (
23 | <>
24 |
25 | {/* eslint-disable-next-line jsx-a11y/accessible-emoji */}
26 | 🤡 Random Programming Jokes 🤡
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 🤡
35 | {' '}
36 | Random Programming Jokes{' '}
37 |
38 |
39 |
40 | {isFetching || !data ? (
41 | '...'
42 | ) : data.type === 'twopart' ? (
43 |
44 |
{data.setup}
45 |
{data.delivery}
46 |
47 | ) : (
48 |
51 | )}
52 |
53 |
54 |
61 |
62 |
63 |
64 |
65 | >
66 | )
67 | }
68 |
69 | IndexPage.layout = AppLayout
70 |
71 | export const getStaticProps: GetServerSideProps = async ({ locale }) => ({
72 | props: {
73 | ...(locale && (await serverSideTranslations(locale, ['common']))),
74 | },
75 | })
76 |
77 | export default IndexPage
78 |
--------------------------------------------------------------------------------
/src/services/jokes.ts:
--------------------------------------------------------------------------------
1 | // Need to use the React-specific entry point to import createApi
2 | import { createApi, fetchBaseQuery } from '@rtk-incubator/rtk-query/react'
3 |
4 | type RandomJokeResponse =
5 | | {
6 | type: 'twopart'
7 | setup: string
8 | delivery: string
9 | }
10 | | {
11 | type: 'single'
12 | joke: string
13 | }
14 |
15 | // Define a service using a base URL and expected endpoints
16 | export const jokeApi = createApi({
17 | reducerPath: 'jokeApi',
18 | baseQuery: fetchBaseQuery({ baseUrl: 'https://v2.jokeapi.dev/' }),
19 | endpoints: builder => ({
20 | getRandomJoke: builder.query({
21 | query: () =>
22 | `joke/Programming?blacklistFlags=explicit,nsfw,political,racist,religious,sexist`,
23 | }),
24 | }),
25 | })
26 |
27 | // Export hooks for usage in functional components, which are
28 | // auto-generated based on the defined endpoints
29 | export const { useGetRandomJokeQuery } = jokeApi
30 |
--------------------------------------------------------------------------------
/src/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit'
2 | import { setupListeners } from '@rtk-incubator/rtk-query'
3 |
4 | import { jokeApi } from './services/jokes'
5 |
6 | export const store = configureStore({
7 | reducer: {
8 | // Add the generated reducer as a specific top-level slice
9 | [jokeApi.reducerPath]: jokeApi.reducer,
10 | },
11 | // Adding the api middleware enables caching, invalidation, polling,
12 | // and other useful features of `rtk-query`.
13 | middleware: getDefaultMiddleware =>
14 | getDefaultMiddleware().concat(jokeApi.middleware),
15 | })
16 |
17 | // optional, but required for refetchOnFocus/refetchOnReconnect behaviors
18 | // see `setupListeners` docs - takes an optional callback as the 2nd arg for customization
19 | setupListeners(store.dispatch)
20 |
--------------------------------------------------------------------------------
/src/styles/Scrollbar.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * inspired by:
3 | * @see https://www.digitalocean.com/community/tutorials/css-scrollbars
4 | */
5 |
6 | /* Firefox */
7 | * {
8 | scrollbar-width: thin;
9 | scrollbar-color: #9ba1ae #ced2d8;
10 | }
11 |
12 | /* width (vertical bar) and height (horizontal bar) */
13 | *::-webkit-scrollbar {
14 | width: 8px; /* width of the entire scrollbar */
15 | height: 8px; /* height of the entire scrollbar */
16 | }
17 |
18 | /* Track */
19 | *::-webkit-scrollbar-track {
20 | @apply bg-gray-300; /* color of the tracking area */
21 | }
22 |
23 | /* Handle */
24 | *::-webkit-scrollbar-thumb {
25 | @apply bg-gray-400; /* color of the scroll thumb */
26 | @apply border-gray-300 border border-solid; /* creates padding around scroll thumb */
27 | @apply rounded-xl; /* roundness of the scroll thumb */
28 |
29 | &:hover {
30 | @apply bg-gray-500;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss/base';
2 | @import 'tailwindcss/components';
3 | @import 'tailwindcss/utilities';
4 |
5 | @import './Scrollbar.scss';
6 |
--------------------------------------------------------------------------------
/src/types/next.d.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react'
2 | import { NextPage } from 'next'
3 |
4 | declare type NextPageType = NextPage & { layout: ReactNode }
5 |
--------------------------------------------------------------------------------
/stylelint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ignoreFiles: [
3 | '**/__generated__/**/*',
4 | '**/__snapshots__/**/*',
5 | '**/build',
6 | '**/coverage/**/*',
7 | '**/node_modules',
8 | '**/storybook-static',
9 | '**/tmp',
10 | ],
11 | extends: ['stylelint-config-recommended'],
12 | rules: {
13 | 'at-rule-no-unknown': [
14 | true,
15 | {
16 | ignoreAtRules: [
17 | 'tailwind',
18 | 'apply',
19 | 'variants',
20 | 'responsive',
21 | 'screen',
22 | ],
23 | },
24 | ],
25 | },
26 | }
27 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | purge: ['./src/**/*.{ts,tsx,scss}'],
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "esnext",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "skipLibCheck": true,
7 | "allowJs": false,
8 | "jsx": "preserve",
9 | "removeComments": true,
10 | "baseUrl": ".",
11 | "paths": {
12 | "@/*": ["src/*", "*"],
13 | },
14 | "noEmit": true,
15 | "importHelpers": true,
16 | "isolatedModules": true,
17 | "strict": true,
18 | "noImplicitAny": true,
19 | "strictNullChecks": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noImplicitReturns": true,
23 | "moduleResolution": "node",
24 | "resolveJsonModule": true,
25 | "allowSyntheticDefaultImports": true,
26 | "forceConsistentCasingInFileNames": true,
27 | "esModuleInterop": true,
28 | },
29 | /** either exclude cypress _or_ only include src/ files to ensure that Cypress'
30 | * and jest's `expect` types don't interfere with each other.
31 | *
32 | * inspired by:
33 | * @see https://github.com/cypress-io/cypress-and-jest-typescript-example/blob/734a38e2451f6cf0341190959a24a4ca40b8ecb7/tsconfig.json
34 | */
35 | "include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts"],
36 | "exclude": ["build", "node_modules", "storybook-static", "tmp"]
37 | }
38 |
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "jsx": "react"
5 | }
6 | }
--------------------------------------------------------------------------------