LogoCésar Alberca
- 6 minutos

Crea un proyecto Angular 17

Diseña Mejor Arquitectura Frontend
Patrones de Arquitectura Frontend complejos explicados de forma sencilla. Envíos recurrentes y directos a tu correo. Conviértete en un mejor Frontend Architect.
Respeto tu privacidad. Cancela en cualquier momento.

La newsletter está escrita en inglés.

#Creación del Proyecto

El primer paso es crear un nuevo proyecto Angular desde cero usando el siguiente comando:

npm init @angular **ng-future**

Seleccionamos la opción "Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)", que ya está preparada para entornos de producción.

Con la opción SSR habilitada, Angular creará dos entornos de ejecución: cliente y servidor. El aspecto novedoso es que está totalmente integrado en la Angular CLI. El SSR mejora el rendimiento y otras métricas importantes sin necesidad de instalar el paquete Angular Universal, que pasaría a estar obsoleto.

#Estructura del Proyecto

Si examinamos la estructura del proyecto, veremos que se han creado una serie de archivos de configuración tanto para la parte del cliente como para la del servidor. Aquí hay un resumen:

  • main.ts ➜ main.server.ts
  • app.config.ts ➜ app.config.server.ts
  • server.ts (Archivo solo para el servidor)

#server.ts

Este archivo inicializa un servidor NodeJSSe abre en una nueva pestaña con ExpressSe abre en una nueva pestaña con el siguiente contenido:

import { APP_BASE_HREF } from '@angular/common' import { CommonEngine } from '@angular/ssr' import express from 'express' import { fileURLToPath } from 'node:url' import { dirname, join, resolve } from 'node:path' import bootstrap from './src/main.server' // La aplicación Express se exporta para que pueda ser utilizada por funciones serverless. export function app(): express.Express { const server = express() const serverDistFolder = dirname(fileURLToPath(import.meta.url)) const browserDistFolder = resolve(serverDistFolder, '../browser') const indexHtml = join(serverDistFolder, 'index.server.html') const commonEngine = new CommonEngine() server.set('view engine', 'html') server.set('views', browserDistFolder) // Servir archivos estáticos desde /browser server.get( '*.*', express.static(browserDistFolder, { maxAge: '1y', }), ) // Todas las rutas regulares usan el motor de Angular server.get('*', (req, res, next) => { const { protocol, originalUrl, baseUrl, headers } = req commonEngine .render({ bootstrap, documentFilePath: indexHtml, url: `${protocol}://${headers.host}${originalUrl}`, publicPath: browserDistFolder, providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }], }) .then(html => res.send(html)) .catch(err => next(err)) }) return server } function run(): void { const port = process.env['PORT'] || 4000 // Iniciar el servidor Node const server = app() server.listen(port, () => { console.log(`Servidor Node Express escuchando en http://localhost:${port}`) }) } run()

Arranca la aplicación del servidor y expone los recursos estáticos (CSS, HTML, fuentes, imágenes, etc.) con el siguiente código:

server.get( '*.*', express.static(browserDistFolder, { maxAge: '1y', }), )

Aquí, se podría integrar un CDNSe abre en una nueva pestaña como CloudFront para servir archivos estáticos y cachearlos.

#main.server.ts

En este archivo, tenemos una función bootstrap exportada que se usa en server.ts para lanzar la aplicación del lado del servidor. También se carga el componente raíz AppComponent. Cabe destacar que no hay mención de los Módulos de Angular, ya que se utilizan los nuevos Standalone Components (profundizaremos en esto en otra sección):

import { bootstrapApplication } from '@angular/platform-browser' import { AppComponent } from './app/app.component' import { config } from './app/app.config.server' const bootstrap = () => bootstrapApplication(AppComponent, config) export default bootstrap

#main.ts

Este archivo contiene el código encargado de lanzar la aplicación del lado del cliente. Es muy similar a main.server.ts excepto por el uso de una configuración diferente, utilizando app.config:

import { bootstrapApplication } from '@angular/platform-browser' import { appConfig } from './app/app.config' import { AppComponent } from './app/app.component' bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err))

#app.config.ts

La configuración en appConfig incluye un nuevo proveedor que cierra la brecha entre el cliente y el servidor: provideClientHydration(). Con este nuevo proveedor, nos aseguramos de que el cliente y el servidor no dupliquen su trabajo.

import { ApplicationConfig } from '@angular/core' import { provideRouter } from '@angular/router' import { routes } from './app.routes' import { provideClientHydration } from '@angular/platform-browser' export const appConfig: ApplicationConfig = { providers: [provideRouter(routes), provideClientHydration()], }

#app.config.server.ts

Finalmente, tenemos app.config.server.ts, que carga un nuevo proveedor: provideServerRendering():

import { ApplicationConfig, mergeApplicationConfig } from '@angular/core' import { provideServerRendering } from '@angular/platform-server' import { appConfig } from './app.config' const serverConfig: ApplicationConfig = { providers: [provideServerRendering()], } export const config = mergeApplicationConfig(appConfig, serverConfig)

#¿Dónde están los NgModules?

Angular anima a los desarrolladores a evitar el uso de módulos, una decisión que creo que es correcta. El principal problema de los módulos es que introducen mucho código repetitivo (boilerplate), lo que facilita pasar por alto una dependencia necesaria, rompiendo potencialmente la aplicación de forma sustancial, y es fácil incluir dependencias innecesarias, añadiendo peso innecesario a un bundle que ya es bastante pesado para una aplicación "Hola, mundo".

#Conclusión

Como hemos visto en este tutorial, hay nuevas configuraciones en comparación con versiones anteriores, simplificando y reduciendo el número de archivos creados con la Angular CLI al comenzar un nuevo proyecto.

Gracias por leer este post
¿Quieres apoyar ideas que merecen ser compartidas? Apoyar este blog me compra tiempo para escribir.
Más tiempo significa más valor para ti.