[Tutorial] - Como criar um boilerplate para projetos com Next.js
Vou mostrar o passo a passo de como realizar todas as configurações que utilizo nos meus projetos com Next.js
Baseado no boilerplate-apps-router do curso React Avançado
Passo a Passo
Meu ambiente de desenvolvimento é o Windows com WSL
e VSCode
como editor de código. Escrevi esse tutorial sobre como configurar o WSL
.
Inicializar o projeto:
Execute os comandos abaixo:
mkdir next-boilerplate
cd next-boilerplate
npm init
git init
Dessa forma, foi criado o arquivo package.json
, onde deixei no seguinte molde:
{
"name": "next-boilerplate",
"version": "1.0.0",
"description": "Next.js Boilerplate",
"repository": "https://github.com/diasjoaovitor/next-boilerplate.git",
"author": "João Vitor <[email protected]>",
"license": "MIT"
}
Especificar a versão do Node:
Crie o arquivo .nvmrc
e adicione a versão a ser utilizada:
lts/iron
É obrigatório deixar uma linha em branco ao final do arquivo para que essa configuração funcione corretamente.
Configurar o commit linter
Instale o git-commit-msg-linter:
npm i -D git-commit-msg-linter
Essa biblioteca verifica se a mensagem de um commit
contém um prefixo semântico como feat
, fix
, refactor
e demais convenções.
Crie o arquivo .gitignore
e adicione a pasta node_modules
Instalar o Next
Instale o next, react e react-dom
npm i next react react-dom
Crie o diretório src/app
e adicione os arquivos layout.jsx
e page.jsx
export const metadata = {
title: 'Next Boilerplate',
description: 'Boilerplate para projetos Next.js'
}
const RootLayout = ({ children }) => {
return (
<html lang="pt-br">
<body>{children}</body>
</html>
)
}
export default RootLayout
const Home = () => {
return <h1>Home</h1>
}
export default Home
Adicione os scripts
no arquivo package.json
:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
Ignore a pasta .next
no arquivo .gitignore
Configurar as regras do editor e do código
Instale o módulo e a extensão do prettier:
npm i -D prettier
Crie o arquivo .prettierrc.json
:
{
"trailingComma": "none",
"semi": false,
"singleQuote": true
}
"trailingComma": "none"
: Isso indica que o Prettier não deve adicionar vírgulas ao final de listas, objetos ou parâmetros de função quando eles são formatados em várias linhas."semi": false
: Isso significa que o Prettier não deve adicionar ponto e vírgula ao final de cada instrução."singleQuote": true
: Isso especifica que o Prettier deve usar aspas simples em vez de aspas duplas, sempre que possível.
Adicione os scripts
:
{
"scripts": {
"prettier:check": "prettier --check .",
"prettier:fix": "prettier --write ."
}
}
Defina o prettier
como o formatador padrão do VSCode em Default Formatter
e habilite a opção Format On Save
Crie a pasta .vscode
e inclua o arquivo settings.json
:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.autoSave": "off",
"git.autofetch": true
}
"editor.formatOnSave": true
- Esta opção faz com que o VSCode formate automaticamente o código quando você salva um arquivo. Isso ajuda a manter o código limpo e consistente com as regras de formatação definidas."editor.defaultFormatter": "esbenp.prettier-vscode"
- Define o Prettier como o formatador de código padrão. O Prettier é uma ferramenta popular que suporta muitas linguagens e estilos de codificação."files.autoSave": "off"
- Desativa o salvamento automático de arquivos. Com essa configuração, os arquivos não serão salvos automaticamente após um período ou quando o foco é alterado; você precisará salvar manualmente suas alterações."git.autofetch": true
- Quando habilitado, o VSCode buscará automaticamente as alterações mais recentes do seu repositório Git periodicamente. Isso é útil para manter seu repositório local atualizado com as alterações remotas.
Crie o arquivo .editorconfig
:
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
-
root = true
: Esta configuração sinaliza que este é o arquivo de configuração principal e que o EditorConfig não deve procurar por outros arquivos de configuração nas pastas acima. -
[*]
: Este é um padrão de correspondência que se aplica a todos os arquivos no projeto. -
indent_style = space
: Define que o estilo de indentação deve ser feito com espaços em vez de tabulações. -
indent_size = 2
: Especifica que o tamanho da indentação deve ser de dois espaços. -
end_of_line = lf
: Indica que o final de linha deve ser formatado usando LF (Line Feed), que é o padrão para sistemas Unix e macOS. -
charset = utf-8
: Define que o conjunto de caracteres do arquivo deve ser UTF-8. -
trim_trailing_whitespace = true
: Quando verdadeiro, remove automaticamente qualquer espaço em branco no final das linhas ao salvar o arquivo. -
insert_final_newline = true
: Garante que haja uma nova linha no final do arquivo ao salvar.
O linting é o processo de aplicar regras a uma base de código e destacar padrões ou códigos problemáticos que não aderem a determinadas diretrizes de estilo. ESLint permite que os desenvolvedores descubram problemas com seu código sem a necessidade de executá-lo.
Instale o eslint, eslint-config-next e o eslint-config-prettier:
npm i -D eslint eslint-config-next eslint-config-prettier
Crie o arquivo .eslintrc.json
:
{
"extends": [
"eslint:recommended",
"next/core-web-vitals",
"prettier"
]]
}
Adicione o script
:
{
"scripts": {
"lint": "next lint",
}
}
Instale o lint-staged e o husky
npm i -D lint-staged husky
Crie o arquivo .lintstagedrc.js
:
const path = require('path')
const buildCommand = (filenames) => {
const files = filenames.map((f) => path.relative(process.cwd(), f))
return [
`npx prettier --write ${files.join(' --file ')}`,
`npx next lint --fix --file ${files.join(' --file ')}`,
]
}
module.exports = {
'*.{js,jsx,ts,tsx}': [buildCommand]
}
Inicie as configurações do husky
:
npx husky init
Dessa forma, foi criado o script prepare
no arquivo package.json
e a pasta .husky
, onde vamos alterar o conteúdo do arquivo pre-commit
para:
npx --no-install lint-staged
Configurar TypeScript
Instale o typescript e demais dependências:
npm i -D typescript @types/node @types/react @types/react-dom @typescript-eslint/eslint-plugin @typescript-eslint/parser
Crie o arquivo tsconfig.json
:
{
"compilerOptions": {
"target": "ESNext", // Especifica a versão do ECMAScript de destino para a qual o código TypeScript será compilado.
"lib": ["dom", "dom.iterable", "esnext"], // Define as bibliotecas de declaração de tipo que serão incluídas na compilação.
"allowJs": true, // Permite a compilação de arquivos JavaScript junto com arquivos TypeScript.
"skipLibCheck": true, // Pula a verificação de tipos em todas as declarações de bibliotecas de tipos (*.d.ts).
"strict": true, // Habilita um conjunto de verificações de tipo mais rigorosas.
"noEmit": true, // Não emite arquivos de saída (como .js) após a compilação.
"esModuleInterop": true, // Habilita a interoperabilidade de módulos ES6 com módulos CommonJS/AMD/UMD.
"module": "esnext", // Define o formato do módulo de saída.
"moduleResolution": "bundler", // Especifica a estratégia de resolução de módulos.
"resolveJsonModule": true, // Permite a importação de arquivos .json.
"isolatedModules": true, // Garante que cada arquivo possa ser compilado de forma isolada.
"jsx": "preserve", // Preserva as anotações JSX no arquivo de saída.
"incremental": true, // Habilita a compilação incremental para acelerar as compilações subsequentes.
"plugins": [
// Lista de plugins do compilador TypeScript.
{
"name": "next" // Especifica o plugin 'next' para ser usado durante a compilação.
}
],
"paths": {
// Define um conjunto de entradas de caminho que serão resolvidas durante a compilação.
"@/*": ["./src/*"] // Permite o uso de '@' como um alias para o diretório './src/'.
}
},
"include": [
// Especifica os arquivos que devem ser incluídos na compilação.
"next-env.d.ts", // Arquivo de declaração de tipo específico do Next.js.
"**/*.ts", // Todos os arquivos TypeScript no projeto.
"**/*.tsx", // Todos os arquivos TypeScript com JSX no projeto.
".next/types/**/*.ts" // Arquivos de tipo específicos do Next.js.
],
"exclude": ["node_modules"] // Exclui a pasta 'node_modules' da compilação.
}
Adicione as configurações de lint recomendadas no arquivo .eslintrc.json
:
{
"extends": [
"eslint:recommended",
"next/core-web-vitals",
"next/typescript",
"prettier"
]
}
Altere as extensões dos arquivos layout
e page
para .tsx
e defina os tipos:
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Next Boilerplate',
description: 'Boilerplate para projetos Next.js'
}
const RootLayout = ({
children
}: Readonly<{
children: React.ReactNode
}>) => {
return (
<html lang="pt-br">
<body className={inter.className}>{children}</body>
</html>
)
}
export default RootLayout
Configurar testes automatizados
Instale as bibliotecas de testes:
npm i -D jest @types/jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event eslint-plugin-jest
Crie os arquivos jest.config.js
e jest.setup.ts
:
const nextJest = require('next/jest')
const createJestConfig = nextJest({
dir: '.'
})
const jestConfig = createJestConfig({
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts']
})
module.exports = jestConfig
import '@testing-library/jest-dom'
Adicione os scripts
:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watchAll"
}
}
Adicione o plugin do eslint no arquivo .eslintrc.json
:
{
"extends": [
"eslint:recommended",
"next/core-web-vitals",
"next/typescript",
"plugin:jest/recommended",
"prettier"
]
}
Adicione o comando para rodar os testes no arquivo lintstagedrc.json
:
const path = require('path')
const buildCommand = (filenames) => {
const files = filenames.map((f) => path.relative(process.cwd(), f))
return [
`npx prettier --write ${files.join(' --file ')}`,
`npx next lint --fix --file ${files.join(' --file ')}`,
`npx jest --runInBand --findRelatedTests ${files.join(' ')} --passWithNoTests`
]
}
module.exports = {
'*.{js,jsx,ts,tsx}': [buildCommand]
}
Configurar Integração Contínua
Adicione o script "test:ci": "jest --runInBand"
no package.json
e crie o arquivo ci.yml
na pasta .github/workflows
:
name: ci
on: [push, pull_request] # O workflow é acionado quando um push ou pull request é aberto, sincronizado ou reaberto.
jobs:
build: # Este é o trabalho de construção que será executado.
runs-on: ubuntu-latest # O trabalho será executado na última versão do Ubuntu disponível.
steps: # Seguem os passos que serão executados em sequência.
uses: actions/checkout@v4 # Este passo faz o checkout do seu repositório usando a ação checkout v4.
uses: actions/setup-node@v4 # Este passo configura o ambiente Node.js usando a ação setup-node v4.
with:
node-version: lts/iron # Define a versão do Node.js para a versão LTS mais recente chamada "Iron".
cache: 'npm' # Habilita o cache para o gerenciador de pacotes NPM.
- name: Install dependencies
run: npm ci # Este passo instala as dependências listadas no arquivo package-lock.json.
- name: Prettier
run: npm run prettier:check # Este passo executa o linting no código para verificar erros de formatação.
- name: Linting
run: npm run lint # Este passo executa o linting no código para verificar erros
- name: Testing
run: npm run test:ci # Este passo executa os testes definidos para o projeto.
- name: Build
run: npm run build # Este passo constrói o projeto.
Configurar gerador de componentes
Instale o Plop
npm i -D plop
Crie a pasta generators
na raiz do projeto e adicione o arquivo plopfile.js
:
const fs = require('fs')
const path = require('path')
module.exports = (plop) => {
plop.setGenerator('component', {
description: 'Create a component',
prompts: [
{
type: 'input',
name: 'name',
message: 'What is your component name?'
}
],
actions: [
{
type: 'add',
path: '../src/app/components/{{pascalCase name}}/index.tsx',
templateFile: 'templates/index.tsx.hbs'
},
{
type: 'add',
path: '../src/app/components/{{pascalCase name}}/{{kebabCase name}}.test.tsx',
templateFile: 'templates/test.tsx.hbs'
},
{
type: 'append',
path: '../src/app/components/index.ts',
template: "export * from './{{pascalCase name}}'",
separator: ''
},
() => {
const indexPath = path.resolve(
__dirname,
'../src/app/components/index.ts'
)
const content = fs.readFileSync(indexPath, 'utf-8')
const lines = content.split('\n')
const updatedContent =
lines
.filter((line) => line.trim() !== '')
.sort()
.join('\n') + '\n'
fs.writeFileSync(indexPath, updatedContent)
}
]
})
}
Crie o arquivo .prettierignore
e ignore todos templates:
*.hbs
Dentro de generators
, crie o templates index.tsx.hbs
e test.tsx.hbs
:
export const {{pascalCase name}} = () => {
return (
<div>{{pascalCase name}}</div>
)
}
import { render, screen } from '@testing-library/react'
import { {{pascalCase name}} } from '.'
describe('<{{pascalCase name}} />', () => {
it('should render component', () => {
render(<{{pascalCase name}} />)
expect(screen.getByText('{{pascalCase name}}')).toBeInTheDocument()
})
})
Adicione o script
:
{
"scripts": {
"generate": "plop --plopfile generators/plopfile.js"
}
}
Crie um componente:
npm run generate MyComponent
Assim, foi gerado um componente e um teste seguindo o padrão dos templates
Dentro da pasta components
, crie o arquivo index.ts
e exporte o componente:
export * from './MyComponent'
Essa configuração ajuda a importar todos os componentes exportados nesse arquivo através do alias @/app/components
Para finalizar, altere o componente MyComponent
e seu arquivo de teste para:
import { PropsWithChildren } from 'react'
export const MyComponent = ({ children }: PropsWithChildren) => {
return <div>{children}</div>
}
import { render, screen } from '@testing-library/react'
import { MyComponent } from '.'
describe('<MyComponent />', () => {
it('should render component', () => {
render(<MyComponent>MyComponent</MyComponent>)
expect(screen.getByText('MyComponent')).toBeInTheDocument()
})
})
Dessa forma, importe o componente no arquivo page.ts
na raiz da pasta app
e crie o teste home.test.tsx
:
import { MyComponent } from '@/app/components'
const Home = () => {
return <MyComponent>Home</MyComponent>
}
export default Home
import { render, screen } from '@testing-library/react'
import Home from './page'
describe('<MyComponent />', () => {
it('should render component', () => {
render(<Home />)
expect(screen.getByText('Home')).toBeInTheDocument()
})
})
Execute npm run dev
para iniciar o app e npm test
para executar os testes.
Considerações finais
Este é um boilerplate
genérico para projetos NextJS
.
O repositório está disponível no meu perfil do GitHub e está aberto a contribuições.