Skip to main content

Introduction

The portfolio application is configured with Angular Universal for server-side rendering (SSR). This provides significant benefits for SEO, initial page load performance, and social media sharing.

Benefits of SSR for Portfolio

SEO Optimization

Search engines can crawl fully-rendered HTML content

Fast First Paint

Users see content immediately without waiting for JavaScript

Social Media

Rich previews work correctly on LinkedIn, Twitter, etc.

Performance

Improved Core Web Vitals and Lighthouse scores

Angular Universal Configuration

The SSR setup is configured in angular.json:
{
  "projects": {
    "portafolio": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "browser": "src/main.ts",
            "server": "src/main.server.ts",
            "outputMode": "server",
            "ssr": {
              "entry": "src/server.ts"
            }
          }
        }
      }
    }
  }
}

Key Configuration Options

  • outputMode: "server": Enables full SSR mode (vs static prerendering)
  • server: "src/main.server.ts": Entry point for Angular server bootstrap
  • ssr.entry: "src/server.ts": Express server configuration file

Server Entry Point

The main server file is located at src/server.ts and uses Express with Angular SSR:
import {
  AngularNodeAppEngine,
  createNodeRequestHandler,
  isMainModule,
  writeResponseToNodeResponse,
} from '@angular/ssr/node';
import express from 'express';
import { join } from 'node:path';

const browserDistFolder = join(import.meta.dirname, '../browser');

const app = express();
const angularApp = new AngularNodeAppEngine();
The AngularNodeAppEngine is the core of Angular SSR. It handles rendering Angular components on the server and returning HTML to the client.

Static File Serving

The server serves static assets with aggressive caching:
app.use(
  express.static(browserDistFolder, {
    maxAge: '1y',
    index: false,
    redirect: false,
  }),
);
Static files are cached for 1 year. Output hashing ensures that file names change when content changes, automatically invalidating old caches.

Request Handling

All non-static requests are handled by Angular:
app.use((req, res, next) => {
  angularApp
    .handle(req)
    .then((response) =>
      response ? writeResponseToNodeResponse(response, res) : next(),
    )
    .catch(next);
});

Server Bootstrap Configuration

The Angular server application is configured in src/app/app.config.server.ts:
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering, withRoutes } from '@angular/ssr';
import { appConfig } from './app.config';
import { serverRoutes } from './app.routes.server';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(withRoutes(serverRoutes))
  ]
};

export const config = mergeApplicationConfig(appConfig, serverConfig);
This configuration:
  • Merges client-side and server-side application configs
  • Provides server rendering capability with route support
  • Uses separate server routes for SSR-specific behavior
The serverRoutes may define different behavior for certain routes during SSR. Always test server-rendered output matches expected client behavior.

Building for SSR

1

Install Dependencies

Ensure all SSR dependencies are installed:
npm install
Key dependencies:
  • @angular/ssr - Angular SSR toolkit
  • @angular/platform-server - Server platform
  • express - Web server framework
2

Build the Application

Build both client and server bundles:
npm run build
This creates:
  • dist/portafolio/browser/ - Client-side files
  • dist/portafolio/server/server.mjs - Server bundle
3

Test Locally

Start the SSR server:
npm run serve:ssr:portafolio
Visit http://localhost:4000 and verify:
  • Page renders correctly
  • View source shows full HTML content (not just loading state)
  • Navigation works smoothly

Running the SSR Server

The SSR server can be started with:
npm run serve:ssr:portafolio
This executes:
node dist/portafolio/server/server.mjs

Server Configuration

The server listens on:
  • Port: Environment variable PORT or default 4000
  • Host: All interfaces (0.0.0.0 in production)
Set the port via environment variable:
PORT=8080 npm run serve:ssr:portafolio
The server automatically detects if it’s the main module or running via PM2, making it compatible with process managers.

Production Deployment

Option 1: Direct Node.js

  1. Build the application:
    npm run build
    
  2. Copy dist/portafolio/ to your server
  3. Install production dependencies:
    npm install --production
    
  4. Start the server:
    NODE_ENV=production PORT=4000 node dist/portafolio/server/server.mjs
    

Option 2: PM2 Process Manager

  1. Install PM2:
    npm install -g pm2
    
  2. Start with PM2:
    pm2 start dist/portafolio/server/server.mjs --name portfolio
    
  3. Configure auto-restart:
    pm2 startup
    pm2 save
    
The server code includes PM2 detection (process.env['pm_id']) and will start correctly when managed by PM2.

Option 3: Docker

Create a Dockerfile:
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --production

COPY dist/portafolio ./dist/portafolio

EXPOSE 4000
ENV PORT=4000
ENV NODE_ENV=production

CMD ["node", "dist/portafolio/server/server.mjs"]
Build and run:
docker build -t portfolio-ssr .
docker run -p 4000:4000 portfolio-ssr

Adding API Endpoints

You can add custom API endpoints to the Express server. Edit src/server.ts:
// Add before the static file serving
app.get('/api/contact', (req, res) => {
  res.json({ email: '[email protected]' });
});

app.post('/api/contact', express.json(), (req, res) => {
  // Handle contact form submission
  res.json({ success: true });
});
API endpoints must be defined before the static file middleware and Angular request handler to ensure they are processed correctly.

Troubleshooting

Server Won’t Start

  • Check that port 4000 is not already in use
  • Verify dist/portafolio/server/server.mjs exists
  • Ensure Node.js 20+ is installed

Blank Page on Load

  • Check browser console for errors
  • Verify static files are served correctly (/browser directory)
  • Test with curl http://localhost:4000 to see server response

Hydration Errors

  • Ensure server and client render the same content
  • Check for browser-only APIs used during SSR
  • Review app.routes.server.ts for route conflicts
Use Chrome DevTools to view the full HTML source (not the rendered DOM) to verify SSR is working. The source should show complete page content before JavaScript executes.

Next Steps

  • Configure reverse proxy (nginx) for production
  • Set up SSL/TLS certificates
  • Implement monitoring and logging
  • Configure CDN for static assets
  • Set up CI/CD pipeline for automated deployments

Build docs developers (and LLMs) love