├── .czrc ├── .env.example ├── .env.test ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .husky ├── commit-msg ├── pre-commit ├── pre-push └── prepare-commit-msg ├── .lintstagedrc.js ├── .prettierrc ├── LICENSE ├── commitlint.config.js ├── jest.config.js ├── jest.setup.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── postcss.config.js ├── readme.md ├── src ├── components │ └── main-heading │ │ ├── __snapshots__ │ │ └── main-heading.spec.tsx.snap │ │ ├── main-heading.spec.tsx │ │ └── main-heading.tsx ├── config │ └── index.ts ├── pages │ ├── _app.tsx │ └── index.tsx └── styles │ └── tailwind.css ├── tailwind.config.js ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "./node_modules/cz-conventional-changelog" 3 | } 4 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_APP_NAME="Starter Project" 2 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_APP_NAME="Starter Project" 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | *.css 4 | .*.js 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "next", 5 | "next/core-web-vitals", 6 | "airbnb", 7 | "airbnb-typescript", 8 | "plugin:sonarjs/recommended", 9 | "prettier" 10 | ], 11 | "plugins": [ 12 | "@typescript-eslint", 13 | "jest", 14 | "unused-imports" 15 | ], 16 | "env": { 17 | "es2021": true, 18 | "browser": true, 19 | "jest": true, 20 | "node": true 21 | }, 22 | "rules": { 23 | "react/require-default-props": "off", 24 | "import/prefer-default-export": "off", 25 | "react/prop-types": "off", 26 | "no-multi-assign": "off", 27 | "import/imports-first": [ 28 | "error", 29 | "absolute-first" 30 | ], 31 | "react/function-component-definition": [ 32 | 1, 33 | { 34 | "namedComponents": "arrow-function", 35 | "unnamedComponents": "arrow-function" 36 | } 37 | ], 38 | "react/jsx-filename-extension": [ 39 | 1, 40 | { 41 | "extensions": [ 42 | ".js", 43 | ".jsx", 44 | ".ts", 45 | ".tsx" 46 | ] 47 | } 48 | ], 49 | // Multiline indentation: https://stackoverflow.com/a/48906878 50 | "indent": ["error", 2], 51 | "react/jsx-indent": ["error", 2], 52 | "react/jsx-indent-props": ["error", 2], 53 | "quotes": [ 54 | 2, 55 | "single", 56 | { 57 | "avoidEscape": true 58 | } 59 | ], 60 | "semi": [ 61 | "error", 62 | "never" 63 | ], 64 | "constructor-super": "error", 65 | "no-invalid-this": "error", 66 | "no-restricted-syntax": [ 67 | "error", 68 | "ForInStatement" 69 | ], 70 | "use-isnan": "error", 71 | "@typescript-eslint/no-unused-vars": "off", 72 | "unused-imports/no-unused-imports": "warn", 73 | "unused-imports/no-unused-vars": [ 74 | "warn", 75 | { 76 | "vars": "all", 77 | "varsIgnorePattern": "^_", 78 | "args": "after-used", 79 | "argsIgnorePattern": "^_" 80 | } 81 | ], 82 | "@typescript-eslint/await-thenable": "error", 83 | "@typescript-eslint/no-floating-promises": "error", 84 | "@typescript-eslint/no-misused-new": "error", 85 | "@typescript-eslint/no-use-before-define": "error", 86 | "@typescript-eslint/restrict-plus-operands": "error", 87 | "@typescript-eslint/no-unnecessary-condition": [ 88 | "error", 89 | { 90 | "allowConstantLoopConditions": true 91 | } 92 | ] 93 | }, 94 | "settings": { 95 | "import/parsers": { 96 | "@typescript-eslint/parser": [ 97 | ".js", 98 | ".jsx", 99 | ".ts", 100 | ".tsx" 101 | ] 102 | }, 103 | "import/resolver": { 104 | "typescript": {} 105 | }, 106 | "react": { 107 | "version": "detect" 108 | } 109 | }, 110 | "parserOptions": { 111 | // Allows for the parsing of modern ECMAScript features 112 | "ecmaVersion": 2021, 113 | // Allows for the use of imports 114 | "sourceType": "module", 115 | // https://blog.geographer.fr/eslint-parser-services, https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project 116 | "project": "./tsconfig.json" 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | .next 4 | .vscode 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /dist 11 | /build 12 | /out 13 | 14 | #cache 15 | /.cache 16 | 17 | # misc 18 | .DS_Store 19 | .Spotlight-V100 20 | Thumbs.db 21 | 22 | # Package 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # Environment Variables 28 | .env 29 | .env.local 30 | 31 | # Typescript 32 | tsconfig.tsbuildinfo 33 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit "${1}" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run test 5 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | # Only run commitizen if no commit message was already provided: https://github.com/commitizen/cz-cli/issues/844#issuecomment-1035862033 5 | if [ -z "${2-}" ]; then 6 | export CZ_TYPE="${CZ_TYPE:-fix}" 7 | export CZ_MAX_HEADER_WIDTH=$COMMITLINT_MAX_WIDTH 8 | export CZ_MAX_LINE_WIDTH=$CZ_MAX_HEADER_WIDTH 9 | # By default git hooks are not interactive. exec < /dev/tty allows a users terminal to interact with commitizen. 10 | exec < /dev/tty && npx cz --hook && npx devmoji -e || true 11 | else 12 | npx devmoji -e || true 13 | fi 14 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // With `next lint`: https://nextjs.org/docs/basic-features/eslint#lint-staged 4 | const buildEslintCommand = (filenames) => 5 | `yarn lint:fix --file ${filenames.map((f) => path.relative(process.cwd(), f)).join(' --file ')}` 6 | 7 | module.exports = { 8 | '*.{js,jsx,ts,tsx}': [buildEslintCommand], 9 | '*.{ts,tsx}': "bash -c 'npm run typecheck'", // running this via bash https://github.com/okonet/lint-staged/issues/825#issuecomment-727185296 10 | } 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "arrowParens": "always", 5 | "noSemi": true, 6 | "semi": false, 7 | "trailingComma": "all", 8 | "tabWidth": 2, 9 | "printWidth": 120, 10 | "importOrderSeparation": false, 11 | "importOrder": [ 12 | "^react", 13 | "^next", 14 | "", 15 | "", 16 | "^@/(.*)$", 17 | "", 18 | "^~/(.*)$", 19 | "", 20 | "^[./]" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Abhishek Bhardwaj 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 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | } 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const nextJest = require('next/jest') 2 | 3 | const createJestConfig = nextJest({ 4 | // Provide the path to your Next.js app to load next.config.js and .env files in your test environment 5 | dir: './', 6 | }) 7 | 8 | // Add any custom config to be passed to Jest 9 | /** @type {import('jest').Config} */ 10 | const customJestConfig = { 11 | setupFilesAfterEnv: ['/jest.setup.ts'], 12 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'], 13 | 14 | // if using TypeScript with a baseUrl set to the root directory then you need the snippet below for alias' to work 15 | moduleDirectories: ['node_modules', '/'], 16 | 17 | testEnvironment: 'jest-environment-jsdom', 18 | testMatch: ['**/*.(test|spec).(js|jsx|ts|tsx)'], 19 | coveragePathIgnorePatterns: ['/node_modules/'], 20 | /** 21 | * Absolute imports and module path aliases 22 | */ 23 | moduleNameMapper: { 24 | '^@/(.*)$': '/src/$1', 25 | '^~/(.*)$': '/public/$1', 26 | }, 27 | } 28 | 29 | // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async 30 | module.exports = createJestConfig(customJestConfig) 31 | -------------------------------------------------------------------------------- /jest.setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/jest-globals' 2 | 3 | // Allow router mocks. 4 | // eslint-disable-next-line no-undef, global-require 5 | jest.mock('next/router', () => require('next-router-mock')) 6 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | poweredByHeader: false, 4 | generateEtags: false, 5 | reactStrictMode: true, 6 | swcMinify: true, 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-boilerplate-typescript-jest", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "export": "yarpm run build && next export", 9 | "lint": "next lint", 10 | "lint:fix": "next lint --fix", 11 | "update-all-dependencies": "npx npm-check-updates -u", 12 | "test:coverage": "yarpm run test --coverage", 13 | "test:debug": "yarpm run test --debug --detectOpenHandles", 14 | "test:ci": "yarpm run test --runInBand --no-cache --ci", 15 | "test": "NODE_ENV=test jest", 16 | "typecheck": "tsc --noEmit", 17 | "clean": "rimraf out/", 18 | "prepare": "is-ci || husky install", 19 | "commit": "cz" 20 | }, 21 | "dependencies": { 22 | "clsx": "^2.0.0", 23 | "is-ci": "^3.0.1", 24 | "next": "^13.4.19", 25 | "react": "^18.2.0", 26 | "react-dom": "^18.2.0" 27 | }, 28 | "devDependencies": { 29 | "@commitlint/cli": "^17.7.1", 30 | "@commitlint/config-conventional": "^17.7.0", 31 | "@ianvs/prettier-plugin-sort-imports": "^4.1.0", 32 | "@testing-library/jest-dom": "^6.1.2", 33 | "@testing-library/react": "^14.0.0", 34 | "@types/jest": "^29.5.4", 35 | "@types/node": "^20.5.7", 36 | "@types/react": "^18.2.21", 37 | "@types/react-dom": "^18.2.7", 38 | "@typescript-eslint/eslint-plugin": "^6.5.0", 39 | "@typescript-eslint/parser": "^6.5.0", 40 | "autoprefixer": "^10.4.15", 41 | "commitizen": "^4.3.0", 42 | "cssnano": "^6.0.1", 43 | "cz-conventional-changelog": "^3.3.0", 44 | "devmoji": "^2.3.0", 45 | "eslint": "^8.48.0", 46 | "eslint-config-airbnb": "19.0.4", 47 | "eslint-config-airbnb-typescript": "^17.1.0", 48 | "eslint-config-next": "^13.4.19", 49 | "eslint-config-prettier": "^9.0.0", 50 | "eslint-config-react-app": "^7.0.1", 51 | "eslint-import-resolver-typescript": "^3.6.0", 52 | "eslint-plugin-import": "^2.28.1", 53 | "eslint-plugin-jest": "^27.2.3", 54 | "eslint-plugin-jsx-a11y": "^6.7.1", 55 | "eslint-plugin-prettier": "^5.0.0", 56 | "eslint-plugin-react": "^7.33.2", 57 | "eslint-plugin-react-hooks": "^4.6.0", 58 | "eslint-plugin-sonarjs": "^0.21.0", 59 | "eslint-plugin-unused-imports": "^3.0.0", 60 | "husky": "^8.0.3", 61 | "jest": "^29.6.4", 62 | "jest-environment-jsdom": "^29.6.4", 63 | "lint-staged": "^14.0.1", 64 | "next-router-mock": "^0.9.9", 65 | "npm-run-all": "^4.1.5", 66 | "postcss": "^8.4.28", 67 | "prettier": "^3.0.2", 68 | "prettier-plugin-tailwindcss": "^0.5.3", 69 | "rimraf": "^5.0.1", 70 | "tailwindcss": "^3.3.3", 71 | "typescript": "^5.2.2", 72 | "yarpm": "^1.2.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | cssnano: {}, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # tailwind-react-next.js-typescript-eslint-jest-starter 2 | 3 | Starter template for building a project using React, Typescript, Next.js, Jest, TailwindCSS and ESLint. 4 | 5 | ## Setup Instructions 6 | 7 | 1. Clone or download the project. 8 | 2. `cd` in the project directory. 9 | 3. If you cloned the project, make sure you remove the remote reference to this project by running `git remote rm origin`. 10 | 4. Copy `.env.example` to `.env` as that file is used to load up all your environment variables. 11 | 4. Run `yarn install` or `npm install` to install all dependencies. 12 | 13 | ## Commands 14 | 15 | - `yarn dev`: To start a local development server. 16 | - `yarn test`: To run the entire unit test suite using `jest`. 17 | - `yarn test:ci`: To run tests on CI. 18 | - `yarn lint`: To run the ESLint based linter to find out the issues in the project. 19 | - `yarn lint:fix`: To autoformat all the issues. 20 | - `yarn export`: Run this after running `yarn analyze` to export a build copy. 21 | - `yarn production`: To export a production build. Use `yarn start` to serve that. 22 | 23 | - `yarn upgrade --latest`: To upgrade all packages to their latest versions (could include breaking changes). 24 | 25 | ## Code Structure 26 | 27 | All source code is located in the `src/` directory. 28 | 29 | 1. All Next.js entrypoints are housed in the `src/pages` directory as a default. 30 | 31 | - Currently has `_app.tsx` which imports TailwindCSS. 32 | - There's also a sample `index.tsx`. 33 | 34 | **NOTE:** Feel free to move `pages` outside of `src/` if that's what you prefer. You'll just need to restart your local development server and everything should continue working as normal. 35 | 36 | 2. `src/components` are all stateless reusable components. 37 | 3. `src/css` folder is there just to house any CSS. 38 | 39 | - Currently contains the TailwindCSS initialization CSS file. 40 | 41 | 4. All env variables are available in `.env` files (`.env` file isn't committed). Whenever you update `.env`, please update `.env.example` and `.env.test` and `next.config.js` to proxy all environment variables properly. 42 | 43 | - You can access these variables in the app source code anywhere using `process.env.`. 44 | 45 | If you feel like changing the directory structure, please change the appropriate settings in the following files: 46 | 47 | - `jest.config.js` 48 | - `postcss.config.js` 49 | - `tsconfig.json` 50 | - The `lint` and the `lint:fix` scripts in `package.json` 51 | -------------------------------------------------------------------------------- /src/components/main-heading/__snapshots__/main-heading.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Index Page renders correctly 1`] = ` 4 |
5 |

8 |

9 | `; 10 | -------------------------------------------------------------------------------- /src/components/main-heading/main-heading.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { render, screen } from '@testing-library/react' 4 | 5 | import MainHeading from './main-heading' 6 | 7 | // Just a sample test. 8 | describe('Index Page', () => { 9 | it('renders correctly', () => { 10 | const { container } = render() 11 | expect(container).toMatchSnapshot() 12 | }) 13 | 14 | it('renders hello world', () => { 15 | // Given 16 | render(Hello World) 17 | 18 | // When 19 | const actual = screen.getByRole('heading', { 20 | name: /hello world/i, 21 | }) 22 | 23 | // Then 24 | expect(actual).toBeInTheDocument() 25 | expect(actual.textContent).toBe('Hello World') 26 | expect(actual.className).toBe('text-xl text-gray-900') 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/components/main-heading/main-heading.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | 3 | const MainHeading: React.FC = ({ children }) =>

{children}

4 | 5 | export interface MainHeadingProps { 6 | children?: ReactNode 7 | } 8 | 9 | export default MainHeading 10 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | export const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME 2 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-props-no-spreading */ 2 | import React from 'react' 3 | import { AppProps } from 'next/app' 4 | 5 | import '@/styles/tailwind.css' 6 | 7 | const App = ({ Component, pageProps }: AppProps) => 8 | 9 | export default App 10 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { NextPage } from 'next' 3 | 4 | import MainHeading from '@/components/main-heading/main-heading' 5 | import { APP_NAME } from '@/config' 6 | 7 | const Home: NextPage = () => ( 8 | 9 | Hello from {APP_NAME}. 10 | 11 | ) 12 | 13 | export default Home 14 | -------------------------------------------------------------------------------- /src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const defaultTheme = require('tailwindcss/defaultTheme') 3 | 4 | /** @type {import('tailwindcss').Config} */ 5 | module.exports = { 6 | content: ['./pages/**/*.{js,jsx,ts,tsx}', './src/**/*.{js,jsx,ts,tsx}'], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "jsx": "react", 6 | "types": ["jest", "node", "@testing-library/jest-dom"], 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "types": ["jest", "node", "@testing-library/jest-dom"], 21 | "baseUrl": ".", 22 | "paths": { 23 | "@/*": [ 24 | "./src/*" 25 | ], 26 | "~/*": [ 27 | "./public/*" 28 | ], 29 | // Types error: https://stackoverflow.com/a/73019448 30 | "react": [ 31 | "./node_modules/@types/react" 32 | ] 33 | }, 34 | "incremental": true 35 | }, 36 | "exclude": [ 37 | "node_modules" 38 | ], 39 | "include": [ 40 | "next-env.d.ts", 41 | "**/*.js", 42 | "**/*.jsx", 43 | "**/*.ts", 44 | "**/*.tsx" 45 | ] 46 | } 47 | --------------------------------------------------------------------------------