- install nvm if not done yet
- use latest node version
nvm use node || nvm install node
-
create nextjs app in the parent folder
npx create-next-app@latest --typescript <project-name> # or pnpm create next-app -- --typescript <project-name>
-
pin nodejs version in the project
node -v > .nvmrc
-
remove the
package.json
andnode_modules/
rm package.json rm -rf node_modules
-
install pnpm globally
npm i -g pnpm
-
install dependencies
pnpm install
-
remove
.eslintrc.json
rm .eslintrc.json
-
install prettier
pnpm i -D prettier eslint-config-prettier eslint-plugin-prettier
-
add
.eslintrc.js
/** @type {import('eslint').Linter.Config} */ module.exports = { extends: ['next', 'prettier', 'plugin:prettier/recommended'], };
-
add
.prettierrc.js
/** @type {import('prettier').Config} */ module.exports = { tabWidth: 2, overrides: [ { files: '*.md', options: { tabWidth: 4, }, }, ], semi: true, singleQuote: true, printWidth: 80, trailingComma: 'es5', };
-
install jest and react-testing-library
pnpm i -D jest @jest/types @testing-library/react @testing-library/jest-dom
-
add
__tests__/
foldermkdir -p __tests__
-
add
__tests__/jest.config.js
const nextJest = require('next/jest'); const createJestConfig = nextJest({ dir: './', }); /** @type {import('@jest/types').Config.InitialOptions} */ const customJestConfig = { rootDir: '../', setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'], moduleDirectories: ['node_modules', '<rootDir>/'], testRegex: '__tests__/.*\\.test\\.tsx?$', testEnvironment: 'jest-environment-jsdom', }; module.exports = createJestConfig(customJestConfig);
-
add
__tests__/jest.setup.ts
import '@testing-library/jest-dom/extend-expect';
-
add to
package.json
{ "scripts": { "test": "jest --config ./__tests__/jest.config.js", "test:watch": "jest --config ./__tests__/jest.config.js --watch" } }
-
install urql and nextjs bindings
pnpm i urql graphql next-urql react-is @urql/core @urql/exchange-graphcache pnpm i -D @urql/devtools
-
add
lib/urql/getUrqlClientOptions.ts
import { devtoolsExchange } from '@urql/devtools'; import { cacheExchange } from '@urql/exchange-graphcache'; import { NextUrqlClientConfig } from 'next-urql'; import { debugExchange, dedupExchange, fetchExchange } from 'urql'; import getIsClient from 'lib/utils/getIsClient'; const getUrqlClientOptions: NextUrqlClientConfig = (ssrCache) => { const isClient = typeof window !== 'undefined'; const isProd = process.env.NEXT_PUBLIC_ENV === 'production'; return { url: process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT || '', exchanges: [ ...(isClient && !isProd ? [devtoolsExchange, debugExchange] : []), dedupExchange, cacheExchange({}), ssrCache, // ssrExchange has to come before fetchExchange fetchExchange, ], }; }; export default getUrqlClientOptions;
-
add graphql query, i.e.
graphql/query/userQuery.ts
export const USER_QUERY = ` query { post(id: 1) { id title body } } `;
-
instantiate graphql client in one of the
getStaticProps
orgetServerSideProps
methodsimport type { GetStaticProps } from 'next'; import getUrqlClientOptions from 'lib/urql/getUrqlClientOptions'; import { initUrqlClient } from 'next-urql'; import { USER_QUERY } from 'graphql/query/userQuery'; import { ssrExchange } from 'urql'; import { WithUrqlState } from 'next-urql'; export interface PageProps {} export interface StaticProps extends WithUrqlState, PageProps {} export const getStaticProps: GetStaticProps<StaticProps> = async () => { const ssrCache = ssrExchange({ isClient: false }); const urqlClientOption = getUrqlClientOptions(ssrCache); const client = initUrqlClient(urqlClientOption, false); await client?.query(USER_QUERY).toPromise(); return { props: { urqlState: ssrCache.extractData(), }, revalidate: 600, }; };
-
add
lib/urql/withStaticUrqlClient.ts
to wrap static generated pagesimport { withUrqlClient } from 'next-urql'; import getUrqlClientOptions from './getUrqlClientOptions'; const withStaticUrqlClient = withUrqlClient(getUrqlClientOptions, { neverSuspend: true, // don't use Suspense on server side ssr: false, // don't generate getInitialProps for the page staleWhileRevalidate: true, // tell client to do network-only data fetching again if the cached data is outdated }); export default withStaticUrqlClient;
-
wrap the page with
withStaticUrqlClient
import withStaticUrqlClient from 'lib/urql/withStaticUrqlClient'; // ... export default withStaticUrqlClient(Page);
-
install graphql codegen dependencies
pnpm i @graphql-typed-document-node/core pnpm i -D @graphql-codegen/cli @graphql-codegen/typed-document-node @graphql-codegen/typescript @graphql-codegen/typescript-operations
-
add
lib/graphql-codegen/codegen.yml
schema: <html-to-graphql-endpoint-or-path-to-server-graphql> documents: './graphql/**/*.graphql' # custom frontend query or mutation defined in graphql generates: ./graphql/generated.ts: plugins: - typescript - typescript-operations - typed-document-node config: fetcher: fetch
-
add to
package.json
{ "scripts": { "codegen": "graphql-codegen --config lib/grpahql-codegen/codegen.yml" } }