mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
docs: update SSR docs (#52408)
This commit updates the `ssr.md` file to restructure (and simplify) SSR docs. PR Close #52408
This commit is contained in:
parent
d122fc4b1b
commit
07623caefe
3 changed files with 93 additions and 212 deletions
|
|
@ -1,10 +1,11 @@
|
|||
// #docplaster
|
||||
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { CommonEngine } from '@angular/ssr';
|
||||
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 {dirname, join, resolve} from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
import bootstrap from './src/main.server';
|
||||
|
||||
// The Express app is exported so that it can be used by serverless Functions.
|
||||
|
|
@ -13,42 +14,34 @@ export function app(): express.Express {
|
|||
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
|
||||
const browserDistFolder = resolve(serverDistFolder, '../browser');
|
||||
const indexHtml = join(serverDistFolder, 'index.server.html');
|
||||
// #docregion CommonEngine
|
||||
const commonEngine = new CommonEngine();
|
||||
// #enddocregion CommonEngine
|
||||
|
||||
server.set('view engine', 'html');
|
||||
server.set('views', browserDistFolder);
|
||||
|
||||
// #docregion data-request
|
||||
// TODO: implement data requests securely
|
||||
// Serve data from URLS that begin "/api/"
|
||||
server.get('/api/**', (req, res) => {
|
||||
res.status(404).send('data requests are not yet supported');
|
||||
});
|
||||
// #enddocregion data-request
|
||||
// #docregion static
|
||||
// Serve static files from /browser
|
||||
server.get('*.*', express.static(browserDistFolder, {
|
||||
maxAge: '1y'
|
||||
}));
|
||||
// #enddocregion static
|
||||
server.get('*.*', express.static(browserDistFolder, {maxAge: '1y'}));
|
||||
|
||||
// #docregion navigation-request
|
||||
// All regular routes use the Angular engine
|
||||
server.get('*', (req, res, next) => {
|
||||
const { protocol, originalUrl, baseUrl, headers } = req;
|
||||
const {protocol, originalUrl, baseUrl, headers} = req;
|
||||
|
||||
commonEngine
|
||||
.render({
|
||||
bootstrap,
|
||||
documentFilePath: indexHtml,
|
||||
url: `${protocol}://${headers.host}${originalUrl}`,
|
||||
publicPath: browserDistFolder,
|
||||
providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
|
||||
})
|
||||
.then((html) => res.send(html))
|
||||
.catch((err) => next(err));
|
||||
.render({
|
||||
bootstrap,
|
||||
documentFilePath: indexHtml,
|
||||
url: `${protocol}://${headers.host}${originalUrl}`,
|
||||
publicPath: browserDistFolder,
|
||||
providers: [{provide: APP_BASE_HREF, useValue: req.baseUrl}],
|
||||
})
|
||||
.then((html) => res.send(html))
|
||||
.catch((err) => next(err));
|
||||
});
|
||||
// #enddocregion navigation-request
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +1,26 @@
|
|||
// #docplaster
|
||||
import { importProvidersFrom } from '@angular/core';
|
||||
import { provideProtractorTestingSupport } from '@angular/platform-browser';
|
||||
import { provideClientHydration} from '@angular/platform-browser';
|
||||
import { ApplicationConfig } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient, withFetch } from '@angular/common/http';
|
||||
import {provideHttpClient, withFetch} from '@angular/common/http';
|
||||
import {ApplicationConfig, importProvidersFrom} from '@angular/core';
|
||||
import {provideClientHydration, provideProtractorTestingSupport} from '@angular/platform-browser';
|
||||
import {provideRouter} from '@angular/router';
|
||||
import {HttpClientInMemoryWebApiModule} from 'angular-in-memory-web-api';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
|
||||
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
|
||||
import { InMemoryDataService } from './in-memory-data.service';
|
||||
import {routes} from './app.routes';
|
||||
import {InMemoryDataService} from './in-memory-data.service';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideRouter(routes),
|
||||
// TODO: Enable using Fetch API when disabling `HttpClientInMemoryWebApiModule`.
|
||||
provideHttpClient(/* withFetch()*/ ),
|
||||
provideClientHydration(),
|
||||
provideProtractorTestingSupport(), // essential for e2e testing
|
||||
// TODO: Enable using Fetch API when disabling `HttpClientInMemoryWebApiModule`.
|
||||
provideHttpClient(/* withFetch()*/), provideClientHydration(),
|
||||
provideProtractorTestingSupport(), // essential for e2e testing
|
||||
|
||||
// #docregion in-mem
|
||||
// TODO: Remove from production apps
|
||||
importProvidersFrom(
|
||||
// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
|
||||
// and returns simulated server responses.
|
||||
// Remove it when a real server is ready to receive requests.
|
||||
HttpClientInMemoryWebApiModule.forRoot(
|
||||
InMemoryDataService, { dataEncapsulation: false }
|
||||
)
|
||||
),
|
||||
// #enddocregion in-mem
|
||||
// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
|
||||
// and returns simulated server responses.
|
||||
// Remove it when a real server is ready to receive requests.
|
||||
HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, {dataEncapsulation: false})),
|
||||
// ...
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,33 +1,26 @@
|
|||
# Server-side rendering (SSR) with Angular Universal
|
||||
# Server-side rendering
|
||||
|
||||
Server-side rendering (SSR) is a process that involves rendering pages on the server, resulting in initial HTML content which contains initial page state. Once the HTML content is delivered to a browser, Angular initializes the application and utilizes the data contained within the HTML.
|
||||
|
||||
Server-Side Rendering (SSR) is a process that involves rendering pages on the server, resulting in static HTML content that mirrors the application's state for each request. Once this server-generated HTML content is produced, Angular initializes the application and utilizes the data contained within the HTML.
|
||||
## Why use SSR?
|
||||
|
||||
The primary advantage of SSR is the enhanced speed at which applications typically render in a browser. This allows users to view the application's user interface before it becomes fully interactive. For more details, refer to the ["Why use Server-Side Rendering?"](#why-use-ssr) section below.
|
||||
The main advantages of SSR as compared to client-side rendering (CSR) are:
|
||||
|
||||
If you're interested in exploring additional techniques and concepts related to SSR, you can refer to this [article](https://developers.google.com/web/updates/2019/02/rendering-on-the-web).
|
||||
* **Improved performance**: SSR can improve the performance of web applications by delivering fully rendered HTML to the client, which can be parsed and displayed even before the application JavaScript is downloaded. This can be especially beneficial for users on low-bandwidth connections or mobile devices.
|
||||
* **Improved Core Web Vitals**: SSR results in performance improvements that can be measured using [Core Web Vitals (CWV)](https://web.dev/learn-core-web-vitals/) statistics, such as reduced First Contentful Paint ([FCP](https://developer.chrome.com/en/docs/lighthouse/performance/first-contentful-paint/)) and Largest Contentful Paint ([LCP](https://web.dev/lcp/)), as well as Cumulative Layout Shift ([CLS](https://web.dev/cls/)).
|
||||
* **Better SEO**: SSR can improve the search engine optimization (SEO) of web applications by making it easier for search engines to crawl and index the content of the application.
|
||||
|
||||
To enable SSR in your Angular application, follow the steps outlined below.
|
||||
## Enable server-side rendering
|
||||
|
||||
<a id="the-example"></a>
|
||||
To create a **new** application with SSR, run:
|
||||
|
||||
## Tutorial
|
||||
<code-example format="shell" language="shell">
|
||||
|
||||
The [Tour of Heroes tutorial](tutorial/tour-of-heroes) is the foundation for this walkthrough.
|
||||
ng new --ssr
|
||||
|
||||
In this example, the Angular application is server rendered using based on client requests.
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
<live-example downloadOnly>Download the finished sample code</live-example>, which runs in a [Node.js® Express](https://expressjs.com) server.
|
||||
|
||||
</div>
|
||||
|
||||
<a id="ssr-cli-command"></a>
|
||||
|
||||
### Step 1. Enable Server-Side Rendering
|
||||
|
||||
To add SSR to an existing project, use the Angular CLI `ng add` command.
|
||||
To add SSR to an **existing** project, use the Angular CLI `ng add` command.
|
||||
|
||||
<code-example format="shell" language="shell">
|
||||
|
||||
|
|
@ -35,13 +28,7 @@ ng add @angular/ssr
|
|||
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
To create an application with server-side rendering capabilities from the beginning use the [ng new --ssr](cli/new) command.
|
||||
|
||||
</div>
|
||||
|
||||
The command updates the application code to enable SSR and adds extra files to the project structure.
|
||||
These commands create and update application code to enable SSR and adds extra files to the project structure.
|
||||
|
||||
<code-example language="text">
|
||||
|
||||
|
|
@ -54,166 +41,76 @@ my-app
|
|||
|
||||
</code-example>
|
||||
|
||||
### Step 2. Run your application in a browser
|
||||
To verify that the application is server-side rendered, run it locally with `ng serve`. The initial HTML request should contain application content.
|
||||
|
||||
Start the development server.
|
||||
## Configure server-side rendering
|
||||
|
||||
<code-example language="shell">
|
||||
|
||||
ng serve
|
||||
|
||||
</code-example>
|
||||
|
||||
After starting the dev-server, open your web browser and visit `http://localhost:4200`.
|
||||
You should see the familiar Tour of Heroes dashboard page.
|
||||
|
||||
Navigation using `routerLinks` works correctly because they use the built-in anchor \(`<a>`\) elements.
|
||||
You can seamlessly move from the Dashboard to the Heroes page and back.
|
||||
Additionally, clicking on a hero within the Dashboard page will display its Details page.
|
||||
|
||||
If you throttle your network speed so that the client-side scripts take longer to download \(instructions following\), you'll notice:
|
||||
|
||||
- You can't add or delete a hero
|
||||
- The search box on the Dashboard page is ignored
|
||||
- The _Back_ and _Save_ buttons on the Details page don't work
|
||||
|
||||
The transition from the server-rendered application to the client application happens quickly on a development machine, but you should always test your applications in real-world scenarios.
|
||||
|
||||
You can simulate a slower network to see the transition more clearly as follows:
|
||||
|
||||
1. Open the Chrome Dev Tools and go to the Network tab.
|
||||
1. Find the [Network Throttling](https://developers.google.com/web/tools/chrome-devtools/network-performance/reference#throttling) dropdown on the far right of the menu bar.
|
||||
1. Try one of the "3G" speeds.
|
||||
|
||||
The server-rendered application still launches quickly but the full client application might take seconds to load.
|
||||
|
||||
## Why use SSR?
|
||||
|
||||
Compared to a client side rendered (CSR) only application the main advantages of SSR are;
|
||||
|
||||
### Improve search engine optimization (SEO)
|
||||
|
||||
Google, Bing, Facebook, Twitter, and other social media sites rely on web crawlers to index your application content and make that content searchable on the web.
|
||||
These web crawlers might be unable to navigate and index your highly interactive Angular application as a human user could do.
|
||||
|
||||
You can generate a static version of your application that is easily searchable, linkable, and navigable without JavaScript and
|
||||
make a site preview available because each URL returns a fully rendered page.
|
||||
|
||||
[Learn more about search engine optimization (SEO)](https://static.googleusercontent.com/media/www.google.com/en//webmasters/docs/search-engine-optimization-starter-guide.pdf).
|
||||
|
||||
### Show the page quicker
|
||||
|
||||
Displaying the page quickly can be critical for user engagement.
|
||||
Pages that load faster perform better, [even with changes as small as 100ms](https://web.dev/shopping-for-speed-on-ebay).
|
||||
Your application might have to launch faster to engage these users before they decide to do something else.
|
||||
|
||||
With server-side rendering, the application doesn't need to wait until all JavaScript has been downloaded and executed to be displayed. In additional HTTP requests done using [`HttpClient`](api/common/http/HttpClient) are done once on the server, as there are cached. See the ["Caching data when using HttpClient"](#caching-data-when-using-httpclient) section below for additional information.
|
||||
|
||||
This results in a performance improvement that can be measured using [Core Web Vitals (CWV)](https://web.dev/learn-core-web-vitals/) statistics, such as reducing the First-contentful paint ([FCP](https://developer.chrome.com/en/docs/lighthouse/performance/first-contentful-paint/)) and Largest Contentful Paint ([LCP](https://web.dev/lcp/)), as well as Cumulative Layout Shift ([CLS](https://web.dev/cls/)).
|
||||
|
||||
### Caching data when using HttpClient
|
||||
|
||||
When [hydration](guide/hydration) is enabled, [`HttpClient`](api/common/http/HttpClient) responses are cached while running on the server and transferring this cache to the client to avoid extra HTTP requests. After that this information is serialized and transferred to a browser as a part of the initial HTML sent from the server after server-side rendering. In a browser, [`HttpClient`](api/common/http/HttpClient) checks whether it has data in the cache and if so, reuses it instead of making a new HTTP request during initial application rendering. HttpClient stops using the cache once an application becomes [stable](api/core/ApplicationRef#isStable) while running in a browser.
|
||||
|
||||
Caching is performed by default for every `HEAD` and `GET` requests. You can include `POST` or filter caching for requests by using the [`withHttpTransferCacheOptions`](/api/platform-browser/withHttpTransferCacheOptions). You can also enable or disable caching by the `transferCache` option in the [HttpClient](/api/common/http/HttpClient) [`post`](/api/common/http/HttpClient#post), [`get`](/api/common/http/HttpClient#get) and [`head`](/api/common/http/HttpClient#head) methods.
|
||||
|
||||
### Rendering engine
|
||||
|
||||
The `server.ts` file configures the SSR rendering engine with Node.js Express server.
|
||||
|
||||
The `CommonEngine` is used to construct the rendering engine.
|
||||
|
||||
<code-example path="ssr/server.ts" region="CommonEngine"></code-example>
|
||||
|
||||
The contructor accepts an object with the following properties:
|
||||
|
||||
| Properties | Details | Default Value |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------- |
|
||||
| _`bootstrap`_ | A method that when invoked returns a promise that returns an `ApplicationRef` instance once resolved or an `NgModule`. | |
|
||||
| _`providers`_ | A set of platform level providers for the all request. | |
|
||||
| _`enablePeformanceProfiler`_ | Enable request performance profiling data collection and printing the results in the server console. | `false` |
|
||||
|
||||
|
||||
The `commonEngine.render()` function which turns a client's requests for Angular pages into server-rendered HTML pages.
|
||||
The `server.ts` file configures a Node.js Express server and Angular server-side rendering. `CommonEngine` is used render an Angular application.
|
||||
|
||||
<code-example path="ssr/server.ts" region="navigation-request"></code-example>
|
||||
|
||||
The function accepts an object with the following properties:
|
||||
The `render` method of `CommonEngine` accepts an object with the following properties:
|
||||
|
||||
| Properties | Details | Default Value |
|
||||
| --------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------- |
|
||||
| _`bootstrap`_ | A method that when invoked returns a promise that returns an `ApplicationRef` instance once resolved or an `NgModule`. | |
|
||||
| _`providers`_ | A set of platform level providers for the current request. | |
|
||||
| `url` | The url of the page to render. | |
|
||||
| _`inlineCriticalCss`_ | Reduce render blocking requests by inlining critical CSS. | `true` |
|
||||
| _`publicPath`_ | Base path for browser files and assets. | |
|
||||
| _`document`_ | The initial DOM to use to bootstrap the server application. | |
|
||||
| _`documentFilePath`_ | File path of the initial DOM to use to bootstrap the server application. | |
|
||||
| Properties | Details | Default Value |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------- | ------------- |
|
||||
| `bootstrap` | A method which returns an `NgModule` or a promise which resolves to an `ApplicationRef`. | |
|
||||
| `providers` | An array of platform level providers for the current request. | |
|
||||
| `url` | The url of the page to render. | |
|
||||
| `inlineCriticalCss` | Whether to reduce render blocking requests by inlining critical CSS. | `true` |
|
||||
| `publicPath` | Base path for browser files and assets. | |
|
||||
| `document` | The initial DOM to use for bootstrapping the server application. | |
|
||||
| `documentFilePath` | File path of the initial DOM to use to bootstrap the server application. | |
|
||||
|
||||
Angular CLI will scaffold an initial server implementation focused on server-side rendering your Angular application. This server can be extended to support other features such as API routes, redirects, static assets, and more. See [Express documentation](https://expressjs.com/) for more details.
|
||||
|
||||
### Working around the browser APIs
|
||||
## Hydration
|
||||
|
||||
Some of the browser APIs and capabilities might be missing on the server.
|
||||
Hydration is the process that restores the server side rendered application on the client. This includes things like reusing the server rendered DOM structures, persisting the application state, transferring application data that was retrieved already by the server, and other processes. Hydration is enabled by default when you use SSR. You can find more info in [the hydration guide](guide/hydration).
|
||||
|
||||
Applications cannot make use of browser-specific global objects like `window`, `document`, `navigator`, or `location`.
|
||||
## Caching data when using HttpClient
|
||||
|
||||
Angular provides some injectable abstractions over these objects, such as [`Location`](api/common/Location) or [`DOCUMENT`](api/common/DOCUMENT); it might substitute adequately for these APIs.
|
||||
If Angular doesn't provide it, it's possible to write new abstractions that delegate to the browser APIs while in the browser and to an alternative implementation while on the server \(also known as shimming\).
|
||||
When SSR is enabled, [`HttpClient`](api/common/http/HttpClient) responses are cached while running on the server. After that this information is serialized and transferred to a browser as a part of the initial HTML sent from the server. In a browser, [`HttpClient`](api/common/http/HttpClient) checks whether it has data in the cache and if so, reuses it instead of making a new HTTP request during initial application rendering. `HttpClient` stops using the cache once an application becomes [stable](api/core/ApplicationRef#isStable) while running in a browser.
|
||||
|
||||
Server-side applications lack access to mouse or keyboard events, which means they can't depend on user interactions such as clicking a button to display a component.
|
||||
In such cases, the application needs to determine what to render solely based on the client's incoming request.
|
||||
This limitation underscores the importance of making the application [routable](guide/router), using a routing mechanism to navigate and display content as needed.
|
||||
Caching is performed by default for all `HEAD` and `GET` requests. You can configure this cache by using [`withHttpTransferCacheOptions`](/api/platform-browser/withHttpTransferCacheOptions) when providing hydration.
|
||||
|
||||
```ts
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [
|
||||
provideClientHydration(withHttpTransferCacheOptions({
|
||||
includePostRequests: true
|
||||
}))
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### Using Angular Service Worker
|
||||
## Authoring server-compatible components
|
||||
|
||||
If you are using Angular on the server in combination with the Angular service worker, the behavior is deviates than the normal server-side rendering behavior. The initial server request will be rendered on the server as expected. However, after that initial request, subsequent requests are handled by the service worker. For subsequent requests, the `index.html` file is served statically and bypasses server-side rendering.
|
||||
Some common browser APIs and capabilities might not be available on the server. Applications cannot make use of browser-specific global objects like `window`, `document`, `navigator`, or `location` as well as certain properties of `HTMLElement`.
|
||||
|
||||
### Filtering request URLs
|
||||
In general, code which relies on browser-specific symbols should only be executed in the browser, not on the server. This can be enforced through the [`afterRender`](api/core/afterRender) and [`afterNextRender`](api/core/afterNextRender) lifecycle hooks. These are only executed on the browser and skipped on the server.
|
||||
|
||||
By default, if the application was only rendered by the server, _every_ application link clicked would arrive at the server as a navigation URL intended for the router.
|
||||
```ts
|
||||
import { Component, ViewChild, afterNextRender } from '@angular/core';
|
||||
|
||||
However, most server implementations have to handle requests for at least three very different kinds of resources: _data_, _application pages_, and _static files_.
|
||||
Fortunately, the URLs for these different requests are easily recognized.
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: `<span #content>{{ ... }}</span>`,
|
||||
})
|
||||
export class MyComponent {
|
||||
@ViewChild('content') contentRef: ElementRef;
|
||||
|
||||
| Routing request types | Details |
|
||||
| :-------------------- | :------------------------------ |
|
||||
| Data request | Request URL that begins `/api` |
|
||||
| Static asset | Request URL with file extension |
|
||||
| App navigation | All other requests |
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
// Safe to check `scrollHeight` because this will only run in the browser, not the server.
|
||||
console.log('content height: ' + this.contentRef.nativeElement.scrollHeight);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `server.ts` generated by the CLI already makes these basic distinctions.
|
||||
You may have to modify it to satisfy your specific application needs.
|
||||
## Using Angular Service Worker
|
||||
|
||||
#### Serving Data
|
||||
|
||||
A Node.js Express server is a pipeline of middleware that filters and processes requests one after the other.
|
||||
|
||||
For data requests, you could configure the Node.js Express server pipeline with calls to `server.get()` as follows:
|
||||
|
||||
<code-example header="server.ts (data API)" path="ssr/server.ts" region="data-request"></code-example>
|
||||
|
||||
HELPFUL: This guide's `server.ts` _doesn't handle data requests_. It returns a `404 - Not Found` for all data API requests.
|
||||
|
||||
For demonstration purposes, this tutorial intercepts all HTTP data calls from the client _before they go to the server_ and simulates the behavior of a remote data server, using Angular's "in-memory web API" demo package.
|
||||
|
||||
In practice, you would remove the following "in-memory web API" code from `app.config.ts`.
|
||||
|
||||
<code-example header="app.config.ts (in-memory web API)" path="ssr/src/app/app.config.ts" region="in-mem"></code-example>
|
||||
|
||||
Then register your data API middleware in `server.ts`.
|
||||
|
||||
#### Serving Static Files Safely
|
||||
|
||||
All static asset requests such as for JavaScript, image, and style files have a file extension (examples: `main.js`, `assets/favicon.ico`, `src/app/styles.css`).
|
||||
They won't be confused with navigation or data requests if you filter for files with an extension.
|
||||
|
||||
To ensure that clients can only download the files that they are permitted to see, put all client-facing asset files in the `dist/my-app/browser` directory.
|
||||
|
||||
The following Node.js Express code routes all requests for files with an extension (`*.*`) to `/dist`, and returns a `404 - NOT FOUND` error if the
|
||||
file isn't found.
|
||||
|
||||
<code-example header="server.ts (static files)" path="ssr/server.ts" region="static"></code-example>
|
||||
If you are using Angular on the server in combination with the Angular service worker, the behavior deviates from the normal server-side rendering behavior. The initial server request will be rendered on the server as expected. However, after that initial request, subsequent requests are handled by the service worker and always client-side rendered.
|
||||
|
||||
<!-- links -->
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue