├── .dockerignore ├── .eslintrc.js ├── .eslintrc.production.js ├── .github ├── renovate.json └── workflows │ └── ci.yml ├── .gitignore ├── .prettierrc ├── .storybook ├── main.js └── webpack.config.js ├── .testcaferc.json ├── .vscode └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── e2e ├── config │ └── index.ts └── specs │ └── HomePage.e2e.ts ├── jest.config.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── serverless.yml ├── src ├── __tests__ │ └── pages │ │ └── about.spec.ts ├── components │ └── AboutUs │ │ ├── AboutUs.stories.tsx │ │ ├── AboutUs.tsx │ │ └── index.ts ├── containers │ ├── AboutPage │ │ ├── AboutPage.stories.tsx │ │ ├── AboutPage.tsx │ │ └── index.ts │ └── HomePage │ │ ├── HomePage.tsx │ │ └── index.tsx ├── pages │ ├── about.tsx │ └── index.tsx └── setupTests.ts ├── tsconfig.jest.json ├── tsconfig.json ├── tsconfig.storybook.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # Next.js 5 | .next 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: './.eslintrc.production.js', 3 | // We can relax some settings here for nicer development experience; warnings will crash in CI 4 | rules: { 5 | '@typescript-eslint/no-unused-vars': [ 6 | 'warn', 7 | { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, 8 | ], 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /.eslintrc.production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | reportUnusedDisableDirectives: true, 5 | plugins: [ 6 | 'react', 7 | 'prettier', 8 | 'jest', 9 | 'react-hooks', 10 | 'import', 11 | 'testing-library', 12 | 'testcafe', 13 | '@typescript-eslint', 14 | ], 15 | extends: [ 16 | 'eslint:recommended', 17 | 'airbnb', 18 | 'prettier', 19 | 'prettier/react', 20 | 'plugin:jest/recommended', 21 | 'plugin:testing-library/react', 22 | 'plugin:testcafe/recommended', 23 | 'plugin:@typescript-eslint/recommended', 24 | ], 25 | env: { 26 | browser: true, 27 | jest: true, 28 | node: true, 29 | es6: true, 30 | }, 31 | rules: { 32 | 'arrow-body-style': ['off'], 33 | 'react/jsx-filename-extension': [1, { extensions: ['.tsx'] }], 34 | 'no-use-before-define': ['off'], 35 | 36 | 'jsx-a11y/anchor-is-valid': ['off'], 37 | 38 | 'jest/expect-expect': ['off'], 39 | 40 | 'import/no-unresolved': ['off'], 41 | 'import/no-extraneous-dependencies': ['off'], 42 | 'import/extensions': ['off'], 43 | 'import/prefer-default-export': ['off'], 44 | 'import/order': [ 45 | 1, 46 | { 47 | 'newlines-between': 'always', 48 | groups: [ 49 | 'builtin', 50 | ['external', 'internal'], 51 | 'parent', 52 | ['sibling', 'index'], 53 | ], 54 | }, 55 | ], 56 | 57 | '@typescript-eslint/explicit-module-boundary-types': 'off', 58 | '@typescript-eslint/no-unused-vars': [ 59 | 'error', 60 | { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, 61 | ], 62 | }, 63 | overrides: [ 64 | { 65 | files: ['*.e2e.ts'], 66 | rules: { 67 | 'jest/expect-expect': ['off'], 68 | 'jest/no-test-callback': ['off'], 69 | 'jest/no-done-callback': ['off'], 70 | }, 71 | }, 72 | ], 73 | }; 74 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "labels": ["dependencies", "automerge"], 4 | "packageRules": [ 5 | { 6 | "groupName": "monorepo:ky", 7 | "packageNames": [ 8 | "ky", 9 | "ky-universal" 10 | ] 11 | }, 12 | { 13 | "groupName": "monorepo:@testing-library", 14 | "packageNames": [ 15 | "@testing-library/jest-dom", 16 | "@testing-library/react", 17 | "@testing-library/user-event" 18 | ] 19 | }, 20 | { 21 | "groupName": "monorepo:testcafe", 22 | "packageNames": [ 23 | "testcafe", 24 | "@testing-library/testcafe" 25 | ] 26 | }, 27 | { 28 | "groupName": "monorepo:eslint", 29 | "packageNames": [ 30 | "@typescript-eslint/eslint-plugin", 31 | "@typescript-eslint/parser", 32 | "eslint", 33 | "eslint-config-airbnb", 34 | "eslint-config-prettier", 35 | "eslint-config-import", 36 | "eslint-plugin-jest", 37 | "eslint-plugin-jsx-a11y", 38 | "eslint-plugin-prettier", 39 | "eslint-plugin-react", 40 | "eslint-plugin-react-hooks", 41 | "eslint-plugin-testcafe", 42 | "eslint-plugin-testing-library" 43 | ] 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | 4 | env: 5 | CI: true 6 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 7 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 8 | AWS_S3_STATE_BUCKET: ${{ secrets.AWS_S3_STATE_BUCKET }} 9 | PR_NAME: master 10 | 11 | jobs: 12 | unit_tests: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 1 19 | - uses: actions/setup-node@v2 20 | with: 21 | node-version: '14.x' 22 | - name: Get yarn cache directory path 23 | id: yarn-cache-dir-path 24 | run: echo "::set-output name=dir::$(yarn cache dir)" 25 | - uses: actions/cache@v2 26 | id: yarn-cache 27 | with: 28 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 29 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 30 | restore-keys: | 31 | ${{ runner.os }}-yarn- 32 | - name: install, lint and unit test 33 | run: | 34 | yarn install --frozen-lockfile 35 | yarn lint 36 | yarn test:unit -- --coverage 37 | - name: Upload coverage to Codecov 38 | uses: codecov/codecov-action@v1 39 | with: 40 | token: ${{ secrets.CODECOV_TOKEN }} 41 | 42 | e2e_tests: 43 | runs-on: ubuntu-latest 44 | env: 45 | ARTIFACTS_PATH: artifacts/ 46 | 47 | steps: 48 | - uses: actions/checkout@v2 49 | with: 50 | fetch-depth: 1 51 | - uses: actions/setup-node@v2 52 | with: 53 | node-version: '14.x' 54 | - name: Get yarn cache directory path 55 | id: yarn-cache-dir-path 56 | run: echo "::set-output name=dir::$(yarn cache dir)" 57 | - uses: actions/cache@v2 58 | id: yarn-cache 59 | with: 60 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 61 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 62 | restore-keys: | 63 | ${{ runner.os }}-yarn- 64 | - name: Install packages 65 | run: | 66 | yarn install --frozen-lockfile 67 | - name: build application 68 | run: yarn build 69 | - name: start application 70 | run: | 71 | mkdir -p $ARTIFACTS_PATH 72 | yarn start 2>&1 | tee $ARTIFACTS_PATH/serve.log & 73 | node_modules/wait-on/bin/wait-on http-get://localhost:3000 --timeout 60000 74 | - name: test:e2e 75 | run: | 76 | mkdir -p $ARTIFACTS_PATH/screenshots 77 | mkdir -p $ARTIFACTS_PATH/videos 78 | xvfb-run --server-args="-screen 0 1280x720x24" yarn test:e2e 79 | - uses: actions/upload-artifact@v2 80 | if: failure() 81 | with: 82 | name: artifacts 83 | path: artifacts 84 | 85 | deploy: 86 | runs-on: ubuntu-latest 87 | needs: [unit_tests, e2e_tests] 88 | if: github.ref == 'refs/heads/master' 89 | 90 | steps: 91 | - uses: actions/checkout@v2 92 | with: 93 | fetch-depth: 1 94 | - uses: actions/setup-node@v2 95 | with: 96 | node-version: '14.x' 97 | - name: Install packages 98 | run: yarn install --frozen-lockfile 99 | - name: Deploy 100 | run: | 101 | aws s3 sync s3://$AWS_S3_STATE_BUCKET/$PR_NAME/.serverless .serverless --delete 102 | npx serverless 103 | aws s3 sync .serverless s3://$AWS_S3_STATE_BUCKET/$PR_NAME/.serverless --delete 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | package-lock.json 4 | 5 | # Next.js 6 | .next 7 | 8 | # Testing 9 | coverage 10 | 11 | # Serveless 12 | .serverless 13 | .serverless_nextjs 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../src/**/*.stories.tsx'], 3 | addons: [ 4 | '@storybook/addon-essentials', 5 | '@storybook/addon-links', 6 | '@storybook/addon-storysource', 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = ({ config }) => { 4 | // Required for absolute imports in Storybook 5 | config.resolve.modules.push(path.resolve(process.cwd(), 'src')); 6 | 7 | config.resolve.extensions.push('.ts', '.tsx'); 8 | return config; 9 | }; 10 | -------------------------------------------------------------------------------- /.testcaferc.json: -------------------------------------------------------------------------------- 1 | { 2 | "quarantineMode": true, 3 | "skipJsErrors": true, 4 | "concurrency": 2, 5 | "screenshots": { 6 | "takeOnFails": true, 7 | "pathPattern": "${USERAGENT}/${FIXTURE}/${TEST}-${QUARANTINE_ATTEMPT}.png", 8 | "path": "artifacts/screenshots/" 9 | }, 10 | "videoPath": "artifacts/videos/", 11 | "videoOptions": { 12 | "failedOnly": true, 13 | "pathPattern": "${USERAGENT}/${FIXTURE}/${TEST}-${QUARANTINE_ATTEMPT}.mp4" 14 | }, 15 | "clientScripts": ["./node_modules/@testing-library/dom/dist/@testing-library/dom.umd.js"] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "files.insertFinalNewline": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true 6 | }, 7 | "[javascript]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode" 9 | }, 10 | "[typescript]": { 11 | "editor.defaultFormatter": "esbenp.prettier-vscode" 12 | }, 13 | "[typescriptreact]": { 14 | "editor.defaultFormatter": "esbenp.prettier-vscode" 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine as build 2 | 3 | COPY . /src 4 | WORKDIR /src 5 | 6 | RUN yarn install --frozen-lockfile 7 | RUN yarn build 8 | RUN yarn install --frozen-lockfile --production --ignore-scripts --prefer-offline 9 | 10 | FROM node:14-alpine 11 | 12 | WORKDIR /usr/app 13 | 14 | COPY --from=build /src/node_modules /usr/app/node_modules 15 | COPY --from=build /src/package.json /usr/app/package.json 16 | COPY --from=build /src/.next /usr/app/.next 17 | 18 | ENV NODE_ENV production 19 | 20 | CMD ["npm", "start"] 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Matej Snuderl 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 | # next-aws-lambda 2 | 3 | [![Actions Status](https://github.com/Meemaw/next-aws-lambda/workflows/website/badge.svg)](https://github.com/Meemaw/next-aws-lambda/actions) [![codecov](https://codecov.io/gh/Meemaw/next-aws-lambda/branch/master/graph/badge.svg)](https://codecov.io/gh/Meemaw/next-aws-lambda) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![Open source: open-source](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)](https://opensource.org/) 4 | 5 | Minimal starter boilerplate project for deploying Serveless [Next.js](https://nextjs.org/) to AWS Lambda@Edge. 6 | 7 | ## Features: 8 | 9 | - Next.js **v10.x** compatible 10 | - Unit testing with [jest](https://jestjs.io/), [RTL](https://testing-library.com/) and [next-page-tester](https://github.com/toomuchdesign/next-page-tester) 11 | - E2E testing with [testcafe](https://devexpress.github.io/testcafe/) 12 | - Linting with [prettier](https://prettier.io/) and [eslint](https://eslint.org/) 13 | - Test coverage using [codecov](https://www.codecov.io/) 14 | - [Github action](https://github.com/features/actions) workflow for out of the box CI/CD 15 | - Storybook 16 | 17 | ## Deployment 18 | 19 | For deployment [serverless-next component](https://github.com/serverless-nextjs/serverless-next.js/) is used: A zero configuration Next.js 10.x serverless component for AWS Lambda@Edge aiming for full feature parity. 20 | 21 | ## Demo 22 | 23 | Demo available [here](https://d2922rq9ynjh4y.cloudfront.net) 24 | -------------------------------------------------------------------------------- /e2e/config/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | baseURL: process.env.BASE_URL || 'http://localhost:3000/', 3 | }; 4 | -------------------------------------------------------------------------------- /e2e/specs/HomePage.e2e.ts: -------------------------------------------------------------------------------- 1 | import 'testcafe'; 2 | import { getByText } from '@testing-library/testcafe'; 3 | 4 | import config from '../config'; 5 | 6 | fixture('').page(config.baseURL); 7 | 8 | test('Can navigate to about page', async (t) => { 9 | await t 10 | .expect(getByText('This is our homepage.').exists) 11 | .ok('Renders HomePage text'); 12 | 13 | await t 14 | .click(getByText('About Us')) 15 | .expect(getByText('We are a cool company.').exists) 16 | .ok('Render AboutPage text'); 17 | }); 18 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@jest/types'; 2 | 3 | const config: Config.InitialOptions = { 4 | transform: { 5 | '^.+\\.tsx?$': 'ts-jest', 6 | }, 7 | setupFilesAfterEnv: ['/src/setupTests.ts'], 8 | globals: { 9 | 'ts-jest': { 10 | tsconfig: 'tsconfig.jest.json', 11 | }, 12 | }, 13 | // Required for absolute imports in Jest 14 | moduleDirectories: ['node_modules', 'src'], 15 | }; 16 | 17 | export default config; 18 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | target: 'serverless', 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-aws-lambda", 3 | "version": "0.0.0-development", 4 | "description": "Minimal starter boilerplate project for Serverless Next.js on AWS Lambda", 5 | "license": "MIT", 6 | "author": "Meemaw ", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/Meemaw/next-aws-lambda" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/Meemaw/next-aws-lambda/issues" 13 | }, 14 | "scripts": { 15 | "dev": "next dev", 16 | "dev:background": "yarn dev 2>&1 > /dev/null &", 17 | "build": "next build", 18 | "start": "next start", 19 | "test": "yarn test:unit", 20 | "test:unit": "jest", 21 | "test:e2e": "testcafe chrome e2e/specs", 22 | "test:e2e:headless": "testcafe chrome:headless e2e/specs", 23 | "lint": "concurrently \"yarn prettier\" \"yarn eslint\"", 24 | "eslint": "eslint --max-warnings 0 'src/**/*.{ts,tsx}' 'e2e/**/*.{ts,tsx}' --config .eslintrc.production.js", 25 | "prettier": "prettier -l 'src/**/*' 'e2e/**/*'", 26 | "prettier:fix": "prettier -l 'src/**/*' 'e2e/**/*' --write", 27 | "storybook": "start-storybook" 28 | }, 29 | "dependencies": { 30 | "ky": "0.25.1", 31 | "ky-universal": "0.8.2", 32 | "next": "10.0.7", 33 | "react": "17.0.2", 34 | "react-dom": "17.0.2" 35 | }, 36 | "devDependencies": { 37 | "@ffmpeg-installer/ffmpeg": "1.0.20", 38 | "@storybook/addon-actions": "6.1.21", 39 | "@storybook/addon-links": "6.1.21", 40 | "@storybook/addon-storysource": "6.1.21", 41 | "@storybook/react": "6.1.21", 42 | "@testing-library/jest-dom": "5.11.9", 43 | "@testing-library/react": "11.2.5", 44 | "@testing-library/testcafe": "4.3.1", 45 | "@testing-library/user-event": "12.7.1", 46 | "@types/jest": "26.0.21", 47 | "@types/node": "14.14.31", 48 | "@types/react": "17.0.2", 49 | "@typescript-eslint/eslint-plugin": "4.18.0", 50 | "@typescript-eslint/parser": "4.18.0", 51 | "babel-loader": "8.2.2", 52 | "concurrently": "6.0.1", 53 | "eslint": "7.22.0", 54 | "eslint-config-airbnb": "18.2.1", 55 | "eslint-config-prettier": "7.2.0", 56 | "eslint-plugin-import": "2.22.1", 57 | "eslint-plugin-jest": "24.3.2", 58 | "eslint-plugin-jsx-a11y": "6.4.1", 59 | "eslint-plugin-prettier": "3.3.1", 60 | "eslint-plugin-react": "7.22.0", 61 | "eslint-plugin-react-hooks": "4.2.0", 62 | "eslint-plugin-testcafe": "0.2.1", 63 | "eslint-plugin-testing-library": "3.10.2", 64 | "jest": "26.6.3", 65 | "next-page-tester": "0.22.0", 66 | "prettier": "2.2.1", 67 | "testcafe": "1.12.0", 68 | "ts-jest": "26.5.1", 69 | "ts-node": "10.0.0", 70 | "typescript": "4.2.2", 71 | "wait-on": "5.3.0" 72 | }, 73 | "husky": { 74 | "hooks": { 75 | "pre-commit": "pretty-quick --staged" 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | name: next-aws-lambda 2 | 3 | website: 4 | component: '@sls-next/serverless-component' 5 | inputs: 6 | memory: 7 | defaultLambda: 1024 8 | apiLambda: 1024 9 | cloudfront: 10 | comment: 'next-aws-lambda (Managed by @sls-next/serverless-component)' 11 | -------------------------------------------------------------------------------- /src/__tests__/pages/about.spec.ts: -------------------------------------------------------------------------------- 1 | import { getPage } from 'next-page-tester'; 2 | import { render, screen } from '@testing-library/react'; 3 | import userEvent from '@testing-library/user-event'; 4 | 5 | describe('/about', () => { 6 | it('Should work', async () => { 7 | const { page } = await getPage({ route: '/about' }); 8 | render(page); 9 | 10 | expect(screen.getByText('We are a cool company.')).toBeInTheDocument(); 11 | expect( 12 | screen.getByText( 13 | '{"userId":1,"id":1,"title":"delectus aut autem","completed":false}' 14 | ) 15 | ).toBeInTheDocument(); 16 | 17 | userEvent.click(screen.getByText('Home')); 18 | 19 | await screen.findByText('This is our homepage.'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/AboutUs/AboutUs.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { Meta } from '@storybook/react'; 3 | 4 | import { AboutUs } from './AboutUs'; 5 | 6 | export default { 7 | title: 'AboutUs', 8 | component: AboutUs, 9 | } as Meta; 10 | 11 | export const Default = () => ; 12 | -------------------------------------------------------------------------------- /src/components/AboutUs/AboutUs.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const AboutUs = () => { 4 | return ( 5 |
6 |

About

7 |

We are a cool company.

8 |
9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /src/components/AboutUs/index.ts: -------------------------------------------------------------------------------- 1 | export { AboutUs } from './AboutUs'; 2 | -------------------------------------------------------------------------------- /src/containers/AboutPage/AboutPage.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { Meta } from '@storybook/react'; 3 | 4 | import { AboutPage } from './AboutPage'; 5 | 6 | export default { 7 | title: 'AboutPage', 8 | component: AboutPage, 9 | } as Meta; 10 | 11 | export const Default = () => ; 12 | -------------------------------------------------------------------------------- /src/containers/AboutPage/AboutPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | import { AboutUs } from 'components/AboutUs'; 4 | import { Todo } from 'pages/about'; 5 | 6 | type Props = { 7 | todos: Todo[]; 8 | }; 9 | 10 | export const AboutPage = ({ todos }: Props) => { 11 | return ( 12 | <> 13 |
    14 |
  • 15 | 16 | Home 17 | 18 |
  • 19 |
  • About Us
  • 20 |
21 | 22 | 23 | 24 |
TODOS:
25 |
{JSON.stringify(todos)}
26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/containers/AboutPage/index.ts: -------------------------------------------------------------------------------- 1 | export { AboutPage } from './AboutPage'; 2 | -------------------------------------------------------------------------------- /src/containers/HomePage/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | 4 | export const HomePage = () => { 5 | return ( 6 | <> 7 |
    8 |
  • Home
  • 9 |
  • 10 | 11 | About Us 12 | 13 |
  • 14 |
15 | 16 |

This is our homepage.

17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/containers/HomePage/index.tsx: -------------------------------------------------------------------------------- 1 | export { HomePage } from './HomePage'; 2 | -------------------------------------------------------------------------------- /src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ky from 'ky-universal'; 3 | import { GetServerSideProps } from 'next'; 4 | import { AboutPage } from 'containers/AboutPage'; 5 | 6 | export type Todo = { 7 | userId: number; 8 | id: number; 9 | title: string; 10 | completed: boolean; 11 | }; 12 | 13 | type Props = { 14 | todos: Todo[]; 15 | }; 16 | 17 | const About = ({ todos }: Props) => { 18 | return ; 19 | }; 20 | 21 | export const getServerSideProps: GetServerSideProps = async (_ctx) => { 22 | const todos = await ky('https://jsonplaceholder.typicode.com/todos/1').json< 23 | Todo[] 24 | >(); 25 | return { props: { todos } }; 26 | }; 27 | 28 | export default About; 29 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { HomePage } from 'containers/HomePage'; 3 | 4 | const Home = () => { 5 | return ; 6 | }; 7 | 8 | export default Home; 9 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import '@testing-library/jest-dom/extend-expect'; 3 | 4 | // Mock global.scrollTo, since Next.js Link component trigger it 5 | global.scrollTo = jest.fn(); 6 | 7 | // Suppress validateDOMNesting error logs 8 | const consoleError = console.error; 9 | console.error = jest.fn((error) => { 10 | if (!error.includes('validateDOMNesting')) { 11 | consoleError(error); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /tsconfig.jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "react" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 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 | "baseUrl": "src" 17 | }, 18 | "exclude": ["node_modules", "e2e"], 19 | "include": ["next-env.d.ts", "src"] 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.storybook.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "react" 5 | } 6 | } 7 | --------------------------------------------------------------------------------