CI/CD para aplicações React/Next.js

1. Fundamentos de CI/CD no ecossistema JavaScript

Integração Contínua (CI) e Entrega/Deploy Contínuo (CD) são práticas essenciais para equipes que desenvolvem aplicações React e Next.js. A CI automatiza a verificação de código a cada commit, executando lint, testes e build. Já a CD automatiza a implantação em ambientes como staging ou produção após a aprovação dos testes.

Um pipeline típico para projetos React/Next.js segue estas etapas:

  1. Trigger: push ou pull request na branch principal
  2. Lint: verificação de estilo com ESLint + Prettier
  3. Type-check: validação de tipos (TypeScript)
  4. Testes: unitários, integração e snapshot
  5. Build: geração dos artefatos otimizados
  6. Deploy: envio para ambiente alvo

Ferramentas populares incluem GitHub Actions, GitLab CI, CircleCI e Jenkins. Neste artigo, focaremos no GitHub Actions por sua integração nativa com repositórios React/Next.js.

2. Configuração de Pipeline com GitHub Actions

Crie o arquivo .github/workflows/ci.yml na raiz do projeto:

name: CI/CD React/Next.js

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1' # toda segunda-feira às 6h

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm run type-check
      - run: npm run test -- --coverage

  build:
    needs: quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: out/ # ou .next para Next.js

Este pipeline executa jobs paralelos de qualidade e build, garantindo que apenas código validado seja implantado.

3. Etapas Essenciais de Teste e Qualidade

Linting e formatação com cache

Configure ESLint e Prettier no package.json:

{
  "scripts": {
    "lint": "eslint src/ --cache --max-warnings 0",
    "format": "prettier --check src/"
  }
}

Testes unitários com Jest e React Testing Library

// src/components/Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';

test('deve renderizar com texto e disparar onClick', () => {
  const handleClick = jest.fn();
  render(<Button onClick={handleClick}>Clique aqui</Button>);

  fireEvent.click(screen.getByText('Clique aqui'));
  expect(handleClick).toHaveBeenCalledTimes(1);
});

Testes de snapshot para componentes

// src/components/Header.test.jsx
import renderer from 'react-test-renderer';
import Header from './Header';

test('snapshot do Header', () => {
  const tree = renderer.create(<Header titulo="Dashboard" />).toJSON();
  expect(tree).toMatchSnapshot();
});

4. Build Otimizado para Ambientes de Produção

Personalize scripts no package.json para cada ambiente:

{
  "scripts": {
    "build:staging": "NEXT_PUBLIC_ENV=staging next build",
    "build:production": "NEXT_PUBLIC_ENV=production next build",
    "build:analyze": "ANALYZE=true next build"
  }
}

Para React puro (Create React App), utilize variáveis de ambiente:

// .env.production
REACT_APP_API_URL=https://api.example.com
REACT_APP_SENTRY_DSN=https://...

// .env.staging
REACT_APP_API_URL=https://staging-api.example.com

Otimizações automáticas do Next.js incluem tree shaking, code splitting e minificação. Para React puro, configure no webpack.config.js:

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin({
      terserOptions: { compress: { drop_console: true } }
    })],
    splitChunks: { chunks: 'all' }
  }
};

5. Estratégias de Deploy para React/Next.js

Deploy contínuo no Vercel

Vercel oferece integração nativa com GitHub. Basta conectar o repositório e configurar:

// vercel.json
{
  "framework": "nextjs",
  "buildCommand": "next build",
  "outputDirectory": ".next",
  "installCommand": "npm ci"
}

Deploy em Cloudflare Pages com Workers

// wrangler.toml
name = "my-react-app"
compatibility_date = "2024-01-01"

[env.production]
routes = [{ pattern = "example.com/*", zone_id = "ZONE_ID" }]

Deploy em servidores próprios com Docker

// Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/out /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
// nginx.conf
server {
    listen 80;
    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }
}

6. Gerenciamento de Ambientes e Segredos

No GitHub Actions, utilize secrets para variáveis sensíveis:

jobs:
  deploy:
    steps:
      - run: echo "API_KEY=${{ secrets.API_KEY }}" >> .env
      - run: npm run build

Configure ambientes no repositório: Settings > Environments. Crie regras de proteção para branches:

// .github/workflows/deploy.yml
on:
  push:
    branches: [main]

jobs:
  deploy-production:
    environment: production
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build

Para PRs, exija aprovação manual e status checks passando antes do merge.

7. Monitoramento e Rollback Pós-Deploy

Integração com Sentry

// next.config.js
const { withSentryConfig } = require('@sentry/nextjs');

module.exports = withSentryConfig({
  sentry: {
    hideSourceMaps: true,
    widenClientFileUpload: true
  }
});

Monitoramento com LogRocket

// pages/_app.js
import LogRocket from 'logrocket';

if (typeof window !== 'undefined') {
  LogRocket.init('YOUR_APP_ID');
}

Estratégias de rollback

  1. Reversão de commit: git revert HEAD e novo push
  2. Deploy anterior: no GitHub Actions, use actions/upload-artifact para manter versões
  3. Feature flags: implemente com LaunchDarkly ou flags simples:
// lib/feature-flags.js
export const FEATURES = {
  novoDashboard: process.env.NEXT_PUBLIC_FEATURE_NOVO_DASHBOARD === 'true'
};

Em caso de erro crítico, desative a flag sem novo deploy.


Referências