├── cypress.json ├── .husky └── pre-commit ├── public ├── favicon.ico └── vercel.svg ├── .prettierrc.json ├── next.config.js ├── cypress ├── fixtures │ └── example.json ├── integration │ └── app.spec.ts ├── support │ ├── index.ts │ └── commands.ts └── plugins │ └── index.ts ├── next-env.d.ts ├── pages ├── _app.tsx ├── api │ └── hello.ts ├── about.tsx └── index.tsx ├── .eslintrc.json ├── styles ├── globals.css └── Home.module.css ├── __tests__ └── index.test.tsx ├── lint-staged.config.js ├── .gitignore ├── tsconfig.json ├── jest.config.ts ├── .github └── workflows │ └── test.yml ├── LICENSE ├── package.json └── README.md /cypress.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wk0/boilerplate-next/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "useTabs": false 7 | } 8 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 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 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import type { AppProps } from 'next/app' 3 | 4 | function MyApp({ Component, pageProps }: AppProps) { 5 | return 6 | } 7 | 8 | export default MyApp 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@typescript-eslint"], 3 | "extends": [ 4 | "next/core-web-vitals", 5 | "plugin:jest-dom/recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "prettier" 8 | ], 9 | "rules": { 10 | "@typescript-eslint/no-unused-vars": "error" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /__tests__/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react' 2 | import Home from '../pages/index' 3 | import '@testing-library/jest-dom' 4 | 5 | describe('Home', () => { 6 | it('renders a heading', () => { 7 | render() 8 | 9 | const heading = screen.getByRole('heading', { 10 | name: /Home Page/i, 11 | }) 12 | 13 | expect(heading).toBeInTheDocument() 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Type check TypeScript files 3 | '**/*.(ts|tsx)': () => 'yarn tsc --noEmit', 4 | 5 | // Lint then format TypeScript and JavaScript files 6 | '**/*.(ts|tsx|js)': (filenames) => [ 7 | `yarn eslint --fix ${filenames.join(' ')}`, 8 | `yarn prettier --write ${filenames.join(' ')}`, 9 | ], 10 | 11 | // Format MarkDown and JSON 12 | '**/*.(md|json)': (filenames) => 13 | `yarn prettier --write ${filenames.join(' ')}`, 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | -------------------------------------------------------------------------------- /cypress/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | // cypress/integration/app.spec.js 2 | 3 | describe('Navigation', () => { 4 | it('should navigate to the about page', () => { 5 | // Start from the index page 6 | cy.visit('http://localhost:3000/') 7 | 8 | // Find a link with an href attribute containing "about" and click it 9 | cy.get('a[href*="about"]').click() 10 | 11 | // The new url should include "/about" 12 | cy.url().should('include', '/about') 13 | 14 | // The new page should contain an h1 with "About page" 15 | cy.get('h1').contains('About Page') 16 | }) 17 | }) 18 | 19 | const asModule = {} 20 | export default asModule 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "types": ["jest", "node", "@types/testing-library__jest-dom"] 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules", "cypress"] 21 | } 22 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import nextJest from "next/jest"; 2 | // Sync object 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 | const customJestConfig = { 10 | // Add more setup options before each test is run 11 | setupFilesAfterEnv: ["@testing-library/jest-dom"], // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work 12 | moduleDirectories: ["node_modules", "/"], 13 | testEnvironment: "jest-environment-jsdom", 14 | modulePathIgnorePatterns: ["cypress"], 15 | }; 16 | 17 | module.exports = createJestConfig(customJestConfig); 18 | -------------------------------------------------------------------------------- /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.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /pages/about.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next' 2 | import Head from 'next/head' 3 | import Link from 'next/link' 4 | 5 | const About: NextPage = () => { 6 | return ( 7 |
8 | 9 | Next App Typescript Boilerplate 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 |
22 |

About Page

23 |
24 | 25 |
26 |

Footer

27 |
28 |
29 | ) 30 | } 31 | 32 | export default About 33 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next' 2 | import Head from 'next/head' 3 | import Link from 'next/link' 4 | 5 | const Home: NextPage = () => { 6 | return ( 7 |
8 | 9 | Next App Typescript Boilerplate 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 |
22 |

Home Page

23 |
24 | 25 |
26 |

Footer

27 |
28 |
29 | ) 30 | } 31 | 32 | export default Home 33 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI - Test 2 | on: 3 | push: 4 | branches: 5 | - '*' 6 | pull_request: 7 | branches: 8 | - '*' 9 | 10 | jobs: 11 | test: 12 | name: Setup 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 5 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: actions/setup-node@v2-beta 18 | with: 19 | node-version: '16.6.2' 20 | check-latest: true 21 | 22 | - name: Install Npm Dependencies 23 | run: yarn install 24 | 25 | - name: Build App 26 | run: yarn build 27 | 28 | - name: Run tests with jest 29 | run: yarn test:ci 30 | 31 | - name: Cypress.io 32 | uses: cypress-io/github-action@v2 33 | with: 34 | start: yarn start 35 | wait-on: 'http://localhost:3000' -------------------------------------------------------------------------------- /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 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any 19 | const loadConfig = (_on: any, _config: any) => { 20 | // `on` is used to hook into various events Cypress emits 21 | // `config` is the resolved Cypress config 22 | } 23 | export default loadConfig 24 | -------------------------------------------------------------------------------- /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 | const asModule = {} 27 | export default asModule 28 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 William Kelly 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boilerplate-next", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "test": "jest --watch", 11 | "test:ci": "jest --ci", 12 | "cypress": "cypress open" 13 | }, 14 | "husky": { 15 | "hooks": { 16 | "pre-commit": "lint-staged" 17 | } 18 | }, 19 | "dependencies": { 20 | "next": "12.1.0", 21 | "react": "17.0.2", 22 | "react-dom": "17.0.2" 23 | }, 24 | "devDependencies": { 25 | "@testing-library/jest-dom": "^5.16.2", 26 | "@testing-library/react": "^12.1.3", 27 | "@types/cypress": "^1.1.3", 28 | "@types/node": "17.0.18", 29 | "@types/react": "17.0.39", 30 | "@types/testing-library__jest-dom": "^5.14.2", 31 | "@types/testing-library__react": "^10.2.0", 32 | "@typescript-eslint/eslint-plugin": "^5.12.0", 33 | "cypress": "^9.5.0", 34 | "eslint": "8.9.0", 35 | "eslint-config-next": "12.1.0", 36 | "eslint-config-prettier": "^8.3.0", 37 | "eslint-plugin-jest-dom": "^4.0.1", 38 | "husky": "^7.0.4", 39 | "jest": "^27.5.1", 40 | "lint-staged": "^12.3.4", 41 | "prettier": "^2.5.1", 42 | "ts-node": "^10.5.0", 43 | "typescript": "4.5.5" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Boilerplate-Next 2 | 3 | Follow along with how I made this via the step-by-step post here on [Medium](https://wk0.medium.com/create-a-typescript-nextjs-project-with-jest-cypress-adbbcf237747) 4 | 5 | Bare-bones 6 | 7 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/wk0/boilerplate-next) 8 | 9 | --- 10 | 11 | With TailwindCSS 12 | 13 | Step by step guide to setting up with Tailwind: [Medium](https://wk0.medium.com/adding-tailwind-to-a-nextjs-typescript-project-d1eba5699c4d) 14 | 15 | [![Deploy with Vercel](https://vercel.com/button)](https://github.com/wk0/boilerplate-next/tree/tailwind) 16 | 17 | --- 18 | 19 | With Web3 20 | 21 | Step by step guide to setting up with Web3: [Medium](https://wk0.medium.com/adding-web3-to-our-nextjs-typescript-project-861e9ed5feaf) 22 | 23 | [![Deploy with Vercel](https://vercel.com/button)](https://github.com/wk0/boilerplate-next/tree/web3-eth) 24 | 25 | --- 26 | 27 | As a Monorepo 28 | 29 | How to convert: [Medium](https://medium.com/p/bf4007fdfa87) 30 | 31 | [![Deploy with Vercel](https://vercel.com/button)](https://github.com/wk0/boilerplate-next/tree/as-workspace) 32 | 33 | 34 | --- 35 | 36 | With Contracts 37 | 38 | How to add hardhat: [Medium](https://medium.com/@wk0/integrating-smart-contracts-using-hardhat-with-nextjs-typescript-7206890b9cd8) 39 | 40 | [![Deploy with Vercel](https://vercel.com/button)](https://github.com/wk0/boilerplate-next/tree/with-contracts) 41 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | --------------------------------------------------------------------------------