Static Site Generation (SSG)
Gopalkrishna Hegde
Application Development Associate Manager @ Accenture | Angular, Cloud, .NET
SSG (Static Site Generation) using Angular
?
What is static site Generation (SSG)?
In Static Site Generation the page HTML is rendered/generated at the build time. It means in production, the page HTML is generated when you run the build.
Steps to Create static page generation in Angular
To build a static website with Angular you can use Angular Universal and the built-in pre-render engine for the static site generation. Let's create an example that demonstrates Static Site Generation (SSG) using Angular. This involves pre-rendering pages at build time and serving them as static HTML files. We'll use Angular Universal's prerendering feature to achieve this.
Here's the step-by-step implementation:
Step-by-Step Implementation
1. Set Up a New Angular Project
First, create a new Angular project and add Angular Universal:
bash
ng new ssgExample --routing
cd ssgExample
ng add @nguniversal/express-engine
2. Create the Standalone Components
Generate standalone components for Home, About, Contact, and Dashboard.
bash
ng generate component home --standalone
ng generate component about --standalone
ng generate component contact --standalone
ng generate component dashboard --standalone
3. Implement the Components
Home Component
src/app/home/home.component.ts
typescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
@Component({
selector: 'app-home',
standalone: true,
imports: [CommonModule, RouterModule],
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent {}
src/app/home/home.component.html
html
<h2>Home</h2>
<p>Welcome to the home page. This page is pre-rendered.</p>
About Component
src/app/about/about.component.ts
typescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
@Component({
selector: 'app-about',
standalone: true,
imports: [CommonModule, RouterModule],
templateUrl: './about.component.html',
styleUrls: ['./about.component.css']
})
export class AboutComponent {}
src/app/about/about.component.html
html
<h2>About</h2>
<p>This is the about page. This page is pre-rendered.</p>
Contact Component (with Reactive Forms)
src/app/contact/contact.component.ts
typescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-contact',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
templateUrl: './contact.component.html',
styleUrls: ['./contact.component.css']
})
export class ContactComponent {
contactForm: FormGroup;
constructor(private fb: FormBuilder) {
this.contactForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
message: ['', [Validators.required, Validators.minLength(10)]]
});
}
onSubmit(): void {
if (this.contactForm.valid) {
console.log('Form Submitted', this.contactForm.value);
}
}
}
src/app/contact/contact.component.html
html
<h2>Contact</h2>
<form [formGroup]="contactForm" (ngSubmit)="onSubmit()">
<div>
<label for="name">Name</label>
<input id="name" formControlName="name" />
<div *ngIf="contactForm.get('name').invalid && contactForm.get('name').touched">
Name is required and must be at least 3 characters long.
</div>
</div>
<div>
<label for="email">Email</label>
<input id="email" formControlName="email" />
<div *ngIf="contactForm.get('email').invalid && contactForm.get('email').touched">
Valid email is required.
</div>
</div>
<div>
<label for="message">Message</label>
<textarea id="message" formControlName="message"></textarea>
<div *ngIf="contactForm.get('message').invalid && contactForm.get('message').touched">
Message is required and must be at least 10 characters long.
</div>
</div>
<button type="submit" [disabled]="contactForm.invalid">Submit</button>
</form>
Dashboard Component
src/app/dashboard/dashboard.component.ts
typescript
领英推荐
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [CommonModule],
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent {}
src/app/dashboard/dashboard.component.html
html
<h2>Dashboard</h2>
<p>This is the dashboard page. This page is client-side rendered.</p>
4. Update Routing for Lazy Loading
Update the routing module to use lazy loading for the standalone components.
src/app/app-routing.module.ts
typescript
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{ path: '', loadComponent: () => import('./home/home.component').then(m => m.HomeComponent) },
{ path: 'about', loadComponent: () => import('./about/about.component').then(m => m.AboutComponent) },
{ path: 'contact', loadComponent: () => import('./contact/contact.component').then(m => m.ContactComponent) },
{ path: 'dashboard', loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent) },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
src/app/app.component.html
html
<nav>
<a routerLink="/">Home</a>
<a routerLink="/about">About</a>
<a routerLink="/contact">Contact</a>
<a routerLink="/dashboard">Dashboard</a>
</nav>
<router-outlet></router-outlet>
?
5. Update the App Module
Ensure the app module is set up correctly to support the standalone components and routing.
src/app/app.module.ts
typescript
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
6. Configure Static Site Generation (SSG)
Update the angular.json file to include SSG configuration.
angular.json
json
?
{
...
"projects": {
"ssgExample": {
...
"architect": {
"build": {
...
},
"server": {
...
},
"prerender": {
"builder": "@nguniversal/builders:prerender",
"options": {
"browserTarget": "ssgExample:build:production",
"serverTarget": "ssgExample:server:production",
"routes": [
"/",
"/about",
"/contact"
]
}
}
}
}
}
}
?
7. Build and Prerender the Application
Build the application for static site generation (SSG):
bash
ng run ssgExample:prerender
Running the Application
After prerendering, you can serve the static files using any static file server. For local testing, you can use a simple HTTP server like http-server.
First, install http-server globally if you don't have it:
bash
npm install -g http-server
Then, serve the pre-rendered static files:
bash
http-server dist/ssgExample/browser
Navigate to https://localhost:8080 in your browser. You should see:
Summary
In this guide, we configured an Angular application for Static Site Generation (SSG) using Angular Universal. The example included:
By configuring the Angular Universal's prerendering feature, we pre-render specific routes at build time and serve them as static HTML files, providing a fast and SEO-friendly experience.
?