Executando verificação de segurança...
2

Angular com server-side rendering (SSR) usando NestJS

Neste artigo, ensino como criar uma aplicação Angular com SSR usando o NestJS como backend. Também compartilho um template já configurado no github que pode ser baixado via arquivo zip.

Introdução

Já teve aquela dificuldade para que seu site feito com Angular renderizasse no servidor (server-side rendering), de modo que o SEO não fosse prejudicado e você mantivesse o conteúdo dinâmico? Eu já.

Por anos utilizei o ng-universal como solução, já que foi criado pelo próprio time do Angular. Como ele tem a opção de usar o expressjs como servidor, era bem fácil trabalhar criando APIs e outras coisas no backend. Claro, além de renderizar a aplicação no servidor antes de entregar para o cliente, que é seu objetivo.

O que é server-side rendering (SSR)?

É muito difícil que um BOT renderize uma página de frameworks como Angular, Vue e React, já que a renderização ocorre via javascript no lado do cliente. A maioria dos bots não executam o javascript da página. Então server-side rendering é obrigatório para sites e aplicações dinâmicas que precisam de entregar um bom SEO. Já aquela aplicação que requer login dá pra ignorar, afinal, os bots nunca vão chegar lá dentro, né?

Mas eu sei que a maioria de vocês já sabia disso. Só expliquei para não deixar aquela fatia pequena na dúvida. É aquele costume na hora de documentar alguma coisa (a gente já tá calejado, né? huehueh).

Um backend poderoso e parecido com o Angular

De toda forma, sempre senti falta de algumas facilidades que encontramos em alguns frameworks de backend. E quem trabalha com backend sabe que criar tudo o zero é um saco. Não é à toa que usamos frameworks parrudos para trabalhar, não é mesmo? E aí eu descobri recentemente que é possível unir a engine do ng-universal ao NestJS, que é um framework bem parrudo para aplicações de backend em Node.

Eu já conhecia (e usava) o NestJS, mas não em conjunto com o Angular e a renderização no servidor (SSR). Também não achei difícil de configurar e, de quebra, a forma como ele é organizado lembra bastante o próprio Angular (que foi inspiração para os criadores do NestJS).

Um template base

Daí pra facilitar o trabalho na hora de criar um novo site com Angular que tenha SSR e me entregue um backend poderoso, eu criei um template base com Angular 16 + NestJS 10 para server-side rendering (SSR). O resultado foi muito satisfatório, mas senti falta de uma coisa: documentação da API com Swagger. Tem um bug no roteamento do Angular que previne o funcionamento (não tá afim de ajudar a resolver não? xD).

Tirando o Swagger, tudo funciona muito bem! Já deixei configurado no template: a localização para o PT-BR, a configuração inicial de testes, API e a estrutura básica de um projeto inicial Angular tradicional. Pq não deixar mais opinionated, né? Esta é uma das vantagens do Angular, afinal de contas, hueheuhe.

E como tudo que é bom a gente compartilha, segue aí para quem quiser usar/contribuir também: veja o template no GitHub. Se quiser baixar para usar direto, use o link para baixar o zip do github.

Fazendo sozinho

E se você quiser fazer sozinho e não usar o template? Boa sorte! Mas vou deixar algumas instruções básicas de como fazer o básico. Elas não contemplam tudo que já deixei pronto no template, mas já são um bom começo.

Crie o Projeto

Inicie o seu projeto com o Angular CLI, normalmente: npx @angular/cli@latest new nomeProjeto

Adicione o SSR

Entre na pasta do projeto criado e adicione a engine SSR do ng-universal criada (é um wrapper) pelo time do NestJS: npx ng add @nestjs/ng-universal

Localize para o Brasil

Altere a tag <html> do index.html para corrigir a língua: <html lang="pt-BR">

Edite o arquivo src/app/app.config.ts (se for standalone) ou src/app/app.module.ts e adicione os seguintes imports:

import { DEFAULT_CURRENCY_CODE, LOCALE_ID } from '@angular/core'
import { DATE_PIPE_DEFAULT_OPTIONS, registerLocaleData } from '@angular/common'
import pt from '@angular/common/locales/pt'
import { provideClientHydration } from '@angular/platform-browser'

Abaixo dos imports, registre a localização:

registerLocaleData(pt)

Por fim, dentro dos providers deste arquivo, adicione:

    provideClientHydration(), // faz o cache de requisições/renderização que já veio do servidor e evita novas chamadas e renderizações no cliente
    { provide: LOCALE_ID, useValue: 'pt_BR' }, // localiza a aplicação para pt_BR
    {
      provide: DATE_PIPE_DEFAULT_OPTIONS, // determina formato padrão da data quando você usar o pipe date
      useValue: { dateFormat: 'dd/MM/yyyy' },
    },
    {
      provide: DEFAULT_CURRENCY_CODE, // determina o REAL como moeda padrão no pipe currency
      useValue: 'BRL',
    }

Configure os testes

Por padrão, os testes do Angular funcionarão sem problemas. Mas não serão testados os arquivos do servidor. Pra isso, você precisa configurar na mão (como eu fiz no template).

Adicione as dependências de testes: npm i -D jest supertest ts-jest @types/supertest @types/jest @nestjs/testing

Agora faça a configuração do jest no final do seu package.json e altere o script de teste:

{
  ...,
  "scripts": {
     ...,
     "test": "ng test && jest",
     ...
  },
  ...,
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "server",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  },
}

Crie seu primeiro endpoint

Para adicionar um endpoint com o NestJS, você deve entrar na pasta server e executar: npx nest g controller teste.

Abra seu controller criado em server/teste/teste.controller.ts e crie um método get:

import { Controller, Get } from '@nestjs/common'

@Controller('teste')
export class TesteController {
  @Get()
  teste() {
    console.log("Nova requisição na API")
    return { msg: 'OK' };
  }
}

Integre seu endpoint no seu frontend

Para o HttpClient do Angular funcionar, você sabe que precisa importar. Então abra seu src/app/app.module.ts ou src/app/app.component.ts (standalone) e adicione o HttpClientModule nos imports ou o provideHttpClient() nos providers (caso standalone).

Agora abra o componente principal (src/app/app.component.ts) e adicione a variável apiStatus e o construtor injetando o HttpClient do Angular e já chamando sua api:

  apiStatus = 'loading'
  constructor(http: HttpClient) {
    http
      .get<{msg:string}>('http://localhost:4200/api/teste')
      .subscribe({
        next: (status) => {
          this.apiStatus = status.msg
        },
        error: (err) => {
          console.error(err)
          this.apiStatus = 'OFF'
        },
      })
  }

Agora no seu HTML (src/app/app.component.html), remova todo o conteúdo e coloque:

<pre>API status: {{ apiStatus }}</pre>

Rodando a aplicação

Pronto, agora inicie a aplicação com npm run dev:ssr e abra o http://localhost:4200 no seu browser. Você deve receber a resposta API status: OK. E no console do servidor, você deve ver apenas um log de chamada na API ('Nova requisição na API'). Isso porque a chamada ocorreu no backend e não foi repetida no frontend, como esperado.

Você pode testar se está ocorrendo renderização no servidor através de um teste simples: abra o postman/insomnia ou o curl e faça uma requisição de teste na raiz do seu servidor (curl localhost:4200 | grep 'API status: OK').

Você deverá ver o HTML renderizado com o <pre ...>API status: OK</pre> em seu conteúdo. Isso demonstra que todo o HTML foi renderizado do lado do servidor e entregue ao cliente sem necessidade de que o javascript chamasse sua API para ter o conteúdo do status da API renderizado no seu browser.

Conclusão

Na verdade, server-side rendering (SSR) é uma prática bem comum e antiga. Vem desde os tempos jurássicos do JSP, ASP, PHP, etc. Frameworks modernos como React, Angular e Vue quebraram este paradigma com as single page applications (SPA). Mas com o advento do SEO e todo tipo de crawler, estes frameworks tiveram que se adaptar e "retroceder". Aí surgiram as engines de SSR do Angular (ng-universal), React (nextjs), Vue (nuxt) e outros para resolver o problema.

O que compartilhei aqui não é para ser uma bala de prata, mas se você quiser ter um backend e frontend robusto numa mesma base de código, Angular + NestJS + SSR é uma alternativa poderosa.

Hugo Sant'Ana (Linkedin)
Fullstack Software Engineer @ Banco do Brasil

Carregando publicação patrocinada...
1

Uma observação que eu uso muito como solução de contorno. Fazer um wordpress para o site da marca/app/empresa e o sistema de fato em outro dominio. tipo:

site da empresa:
minhaempresa.com

o serviço que a empresa vende:
serviço.minhaempresa.com

assim podemos ter o melhor dos dois mundos.

minha solução de contorno apenas.