Skip to content

Instantly share code, notes, and snippets.

@RyanHirsch
Last active June 23, 2022 16:13
Show Gist options
  • Save RyanHirsch/1c058517e013c67c671b213a1d1658b3 to your computer and use it in GitHub Desktop.
Save RyanHirsch/1c058517e013c67c671b213a1d1658b3 to your computer and use it in GitHub Desktop.
Next.js Setup with typescript, jest, tailwind, and MSW.

NextJS setup

yarn create next-app

run setup.sh

Update package.json

  "lint-staged": {
    "*.{js,ts,tsx}": [
      "prettier --write",
      "eslint --ext .js,.ts,.tsx --fix"
    ]
  }

run yarn dev to have Next autogenerate a tsconfig.json file

run yarn fix to "fix" the format of tsconfig.json and be prettier compliant.

NOTE: Tests cannot live inside the pages/ directory as next behavior assumes all files are pages or APIs. It's possible to configure webpack to ignore these files, but it will still interfere with Next's bundle analysis.

Container Dev

Dev Container Extensions

"extensions": [
  "dbaeumer.vscode-eslint",
  "eamodio.gitlens",
  "editorconfig.editorconfig",
  "esbenp.prettier-vscode",
  "orta.vscode-jest",
  "redhat.vscode-yaml",
  "streetsidesoftware.code-spell-checker",
  "wix.vscode-import-cost",
],

Dev Container Port

"forwardPorts": [
  3000
]
touch tsconfig.json
cat << 'EOF' > styles/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
/* purgecss start ignore */
body {
@apply bg-gray-100;
min-height: 100vh;
}
/* purgecss end ignore */
EOF
mkdir -p lib
cat << 'EOF' > lib/config.ts
export const config = {
apiBase: process.env.NEXT_PUBLIC_API_BASE,
};
EOF
cat << 'EOF' > lib/fetch.ts
/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment */
import fetch from "isomorphic-unfetch";
export default async function libFetch(
input: RequestInfo,
init?: RequestInit | undefined
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any> {
const res = await fetch(input, init);
return res.json();
}
EOF
rm -rf pages/api/hello.js
cat << 'EOF' > pages/api/hello.ts
import type { NowRequest, NowResponse } from "@vercel/node";
export default function hello(_req: NowRequest, res: NowResponse): void {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
res.status(200).json({ name: "John Doe" });
};
EOF
rm -f pages/_app.js
cat << 'EOF' > pages/_app.tsx
import { NextComponentType } from "next";
import { AppContext, AppInitialProps, AppProps } from "next/app";
import "../styles/globals.css";
const App: NextComponentType<AppContext, AppInitialProps, AppProps> = ({
Component,
pageProps,
}: AppProps) => {
return <Component {...pageProps} />;
};
export default App;
EOF
rm -rf pages/index.js
cat << 'EOF' > pages/index.tsx
import React from "react";
import { NextPage } from "next";
import Head from "next/head";
import useSWR from "swr";
import libFetch from "../lib/fetch";
import { config } from "../lib/config";
const Index: NextPage = () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data, error } = useSWR<{ name: string }>(`${config.apiBase}/api/hello`, libFetch);
return (
<div className="container">
<Head>
<title>Create Next App</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<header data-testid="hello-world">Hello There!</header>
{data ? (
<div data-testid="result">{data.name}</div>
) : error ? (
<pre>{JSON.stringify(error)}</pre>
) : null}
</div>
);
};
export default Index;
EOF
mkdir -p __tests__
cat << 'EOF' > __tests__/index.test.tsx
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import React from "react";
import { render, screen } from "@testing-library/react";
import { rest } from "msw";
import { setupServer } from "msw/node";
import { config } from "../lib/config";
import Home from "../pages/index";
const server = setupServer(
rest.get(`${config.apiBase}/api/hello`, (_req, res, ctx) => {
return res(ctx.json({ name: "Johnny" }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test("displays greeting", () => {
render(<Home />);
expect(screen.getByTestId("hello-world")).toHaveTextContent("Hello There!");
});
test("displays response", async () => {
render(<Home />);
expect(await screen.findByTestId("result")).toHaveTextContent("Johnny");
});
EOF
cat << 'EOF' > .env.local
NEXT_PUBLIC_API_BASE=
EOF
cat << 'EOF' > .env
NEXT_PUBLIC_API_BASE=
EOF
cat << 'EOF' > .prettierrc
{
"singleQuote": false,
"printWidth": 100,
"trailingComma": "es5",
"arrowParens": "always"
}
EOF
cat << 'EOF' > .prettierignore
.next
__generated__
.now
.vscode/settings.json
.devcontainer
EOF
cat << 'EOF' > .editorconfig
# EditorConfig is awesome: http://EditorConfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = tab
EOF
cat << 'EOF' > .eslintignore
.next
.now
__generated__
EOF
cat << 'EOF' > .eslintrc
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.eslint.json"
},
"plugins": ["@typescript-eslint", "sonarjs"],
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:sonarjs/recommended",
"prettier",
"plugin:prettier/recommended"
],
"rules": {
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/explicit-function-return-type": ["error", { "allowExpressions": true }],
"react/react-in-jsx-scope": "off",
"react/prop-types": "off"
},
"settings": {
"react": {
"version": "detect"
}
}
}
EOF
TEST_URL=http://api.backend.test
cat << EOF > jest.setup.ts
import "@testing-library/jest-dom";
process.env.NEXT_PUBLIC_API_BASE = "$TEST_URL";
EOF
cat << 'EOF' > jest.config.js
/* eslint-env node */
module.exports = {
preset: "ts-jest/presets/js-with-ts",
moduleFileExtensions: ["ts", "tsx", "js"],
moduleNameMapper: {},
transform: {
"^.+.(ts|tsx)$": "ts-jest",
},
testMatch: ["**/__tests__/*.(ts|tsx)"],
setupFilesAfterEnv: ["./jest.setup.ts"],
testPathIgnorePatterns: ["./.next/", "./node_modules/"],
globals: {
"ts-jest": {
tsConfig: "tsconfig.jest.json",
},
},
};
EOF
cat << 'EOF' > postcss.config.js
/* eslint-env node */
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
EOF
cat << 'EOF' > tailwind.config.js
/* eslint-env node */
module.exports = {
purge: ['./pages/**/*.tsx', './components/**/*.tsx'],
darkMode: false,
theme: {
extend: {},
},
variants: {
backgroundColor: ["responsive", "hover", "focus", "active"],
},
plugins: [],
};
EOF
cat << 'EOF' > tsconfig.eslint.json
{
"extends": "./tsconfig.json",
"include": ["**/*.tsx", "**/*.ts", "*.ts", "**/*.js", "*.js", "next-env.d.ts"],
"exclude": ["node_modules", "dist", ".next", "out"]
}
EOF
cat << 'EOF' > tsconfig.jest.json
{
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"noImplicitAny": true,
"sourceMap": true,
"target": "es5"
},
"include": ["**/*.ts", "**/*.tsx"]
}
EOF
yarn add isomorphic-unfetch \
swr \
autoprefixer \
immer \
tailwindcss \
postcss
yarn add --dev typescript \
msw \
@types/react \
@types/node \
@vercel/node \
prettier \
eslint \
eslint-config-prettier \
eslint-plugin-import \
eslint-plugin-prettier \
eslint-plugin-sonarjs \
eslint-plugin-react \
@typescript-eslint/eslint-plugin \
@typescript-eslint/parser \
husky \
lint-staged \
jest \
@testing-library/jest-dom \
@testing-library/react \
ts-jest \
npm-run-all
npx dot-json package.json "scripts.lint" "npm-run-all -p lint:*"
npx dot-json package.json "scripts.lint:tsc" "tsc --noEmit"
npx dot-json package.json "scripts.lint:eslint" "eslint --ext .js,.ts,.tsx ."
npx dot-json package.json "scripts.lint:prettier" "prettier --check ."
npx dot-json package.json "scripts.fix" "prettier --write . && eslint --fix --ext .js,.ts,.tsx ."
npx dot-json package.json "scripts.test" "jest"
npx dot-json package.json "husky.hooks.pre-commit" "tsc --noEmit && lint-staged && yarn test"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment