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