This guide shows you how to deploy a Spartacus application to Edgio.
Prerequisites
Setup requires:
- An Edgio account. Sign up for free.
- An Edgio property. Learn how to create a property.
- Node.js. View supported versions and installation steps.
- Edgio CLI.
Install the Edgio CLI
If you have not already done so, install the Edgio CLI.
Bash
1npm i -g @layer0/cli@latest
Getting Started
If you don’t already have a Spartacus application, you can create one using:
1. Create a new Angular App
Spartacus 2.x only supports Angular version 9.x
Spartacus 3.x only supports Angular version 10.x
Bash
1npm install -g @angular/cli@92ng new my-layer0-spartacus-app
You should now have a working starter app. Run
ng serve
to see the application running on localhost:4200
.2. Add Spartacus with SSR
To deploy your Spartacus application on Edgio it needs to support server-side rendering (SSR). To add SSR support, run:
Bash
1ng add @spartacus/schematics --ssr
Read more about server-side rendering in Spartacus here.
The previous command created:
- A server-side application module (
app.server.module.ts
) - A bootstrapper for the server app (
main.server.ts
) server.ts
which exports an Express app- TypeScript configuration for the server (
tsconfig.server.json
)
You can now run
npm run build:ssr && npm run serve:ssr
to access your server-side rendered app at localhost:4000
.3. Initializing your Project
Initialize your project for use with Edgio by running the following command in your project’s root directory:
Bash
10 init
This will automatically add all of the required dependencies and files to your project. These include:
- The
@layer0/core
package - The
@layer0/angular
package - The
@layer0/cli
package - The
@layer0/spartacus
package - The
@layer0/prefetch
package layer0.config.js
- Contains various configuration options for Edgio.routes.js
- A default routes file that sends all requests to the Angular Universal server. Update this file to add caching or proxy some URLs to a different origin.- The
sw
folder - Contains the files needed to build the service worker that that provides static asset and API prefetching.
4. Update layer0.config.js
For an app called
my-layer0-spartacus-app
the Edgio config file created by 0 init
will look like so:JavaScript
1// This file was automatically added by 0 deploy.2// You should commit this file to source control.34module.exports = {5 backends: {6 commerce: {7 domainOrIp: 'api-commerce.my-site.com',8 hostHeader: 'api-commerce.my-site.com',9 },10 },11}
If you have several projects and the
defaultProject
as specified in angular.json
is not the project with the SSR build, specify the correct project with the ANGULAR_PROJECT
environment variable. For example: ANGULAR_PROJECT=my-ssr-project 0 build
.5. Update OCC baseUrl
endpoint
The
baseUrl
should be updated to use the remote URL when window
is not defined (i.e., for SSR), and the current host when window
is defined. For example:JavaScript
1baseUrl: typeof window !== 'undefined'2 ? `${window.location.protocol}//${window.location.host}`3 : 'https://api-commerce.my-site.com'
This value is defined in the
backend
property of the options parameter to B2cStorefrontModule.withConfig({})
in the app.module.ts
file, but is best set using environment variables in the environment.ts
and environment.prod.ts
files.Adding prefetching
Upstream request tracking
Prefetching for a Spartacus app can be enabled by listening to upstream requests made when server-side rendering a specific page.
@layer0/prefetch
library will pick up on the upstream requests made by reading the x-0-upstream-requests
response header. An example scenario:- User A lands on
/product/1
. /product/1
has not been cached in the edge and thus will be server-side rendered.- The rendering server has been modified to track upstream requests by patching
https.request
. - The rendering server sets
x-0-upstream-requests
to, for example:/rest/v2/1;/rest/v2/2;
- The HTML response for
/product/1
is now cached and for future requests served from the edge along with thex-0-upstream-requests
response header. - User B lands on a page that has a link to
/product/1
./product/:path*
has been configured withcache.browser.spa: true
. Because of this configuration,@layer0/prefetch
will know to make a prefetch HEAD request for/product/1
, and only ifproduct/1
can be served from the edge will it prefetch all requests specified inx-0-upstream-requests
response header. - When User B click the link to
/product/1
, the navigation will be faster since the requests needed to render the new page will be in service worker cache.
Example implementation of upstream request tracking changes required in your
server.ts
file:JavaScript
1import 'zone.js/dist/zone-node'2import * as express from 'express'3import { join } from 'path'45+ // layer06+ import * as http from 'http'7+ import * as https from 'https'8+ import createRenderCallback from '@layer0/spartacus/server/createRenderCallback'9+ import installLayer0Middleware from '@layer0/spartacus/server/installLayer0Middleware'101112// Express server13const server = express()1415+ installLayer0Middleware({ server, http, https });1617const PORT = process.env.PORT || 420018const DIST_FOLDER = join(process.cwd(), 'dist/<your-project-name>')1920// * NOTE :: leave this as require() since this file is built Dynamically from webpack21const {22 AppServerModuleNgFactory,23 LAZY_MODULE_MAP,24 ngExpressEngine,25 provideModuleMap,26} = require('./dist/<your-project-name>-server/main')2728server.engine(29 'html',30 ngExpressEngine({31 bootstrap: AppServerModuleNgFactory,32 providers: [provideModuleMap(LAZY_MODULE_MAP)],33 }),34)3536server.set('view engine', 'html')37server.set('views', DIST_FOLDER)3839server.get(40 '*.*',41 express.static(DIST_FOLDER, {42 maxAge: '1y',43 }),44)4546// All regular routes use the Universal engine47server.get('*', (req, res) => {48 res.render(49 'index',50 { req },51+ createRenderCallback(res),52 )53})5455export default server
Fixing response header overflows
Some CDNs, such as Akamai, impose low limits on the size of response headers. Prefetching works by listing all of the upstream API URLs fetched during SSR in
a
x-0-upstream-requests
response header. If your application makes many upstream requests for each page during SSR, this header can
be quite long and exceed the maximum length allowed by your CDN. To mitigate this, using the maxHeaderLength
option when calling createRenderCallback
:JavaScript
1createRenderCallback(res, { maxHeaderLength: 500 })
Service worker
The build command places the built
service-worker.js
under dist
so @layer0/angular
will know to static serve the file.Installing the service worker and any further prefetching will be handled by
@layer0/prefetch
by invoking the install
function imported from @layer0/prefetch/window/install
.Example implementation in
app.component.ts
:JavaScript
1import { Component, OnInit, Inject } from '@angular/core'2import { isPlatformBrowser } from '@angular/common'3import { PLATFORM_ID } from '@angular/core'4+ import install from '@layer0/prefetch/window/install'56@Component({7 selector: 'app-root',8 templateUrl: './app.component.html',9 styleUrls: ['./app.component.scss'],10})11export class AppComponent implements OnInit {12 isBrowser: boolean13 title = '<your-project-name>'1415 constructor(@Inject(PLATFORM_ID) platformId: Object) {16 this.isBrowser = isPlatformBrowser(platformId)17 }1819 ngOnInit() {20+ setTimeout(() => {21+ if (this.isBrowser) {22+ install()23+ }24+ })25 }26}
To avoid Spartacus installing
ngsw-worker
, set production: false
in environment.prod.ts
as a temporary workaround:Diff
1pwa: {2- enabled: environment.production3+ enabled: false4},
You may also need to disable it in your
app.module.ts
file:Diff
1ServiceWorkerModule.register(2 'ngsw-worker.js',3 {4- enabled: environment.production,5+ enabled: false6 }7),
Add
"skipLibCheck": true,
to tsconfig.json
to avoid type errors from workbox
library during build.Routing and Cache Configuration
The default
routes.js
file created by 0 init
sends all requests to Angular server via a fallback route.JavaScript
1// This file was automatically added by 0 deploy.2// You should commit this file to source control.34import { Router } from '@layer0/core/router'5import { angularRoutes } from '@layer0/angular'67export default new Router()8 // Prevent search engine bot(s) from indexing9 // Read more on: https://docs.layer0.co/applications/cookbook#blocking-search-engine-crawlers10 .noIndexPermalink()11 .use(angularRoutes)
The default router also includes common cache configurations for most Spartacus apps:
JavaScript
1return new Router()2 // Prevent search engine bot(s) from indexing3 // Read more on: https://docs.layer0.co/applications/cookbook#blocking-search-engine-crawlers4 .noIndexPermalink()5 .match('/rest/v2/:path*', ({ cache, proxy }) => {6 cache({7 browser: {8 maxAgeSeconds: PAGE_TTL,9 serviceWorkerSeconds: PAGE_TTL,10 },11 edge: {12 maxAgeSeconds: PAGE_TTL,13 staleWhileRevalidateSeconds: PAGE_TTL,14 },15 })16 return proxy('commerce')17 })18 .match('/medias/:path*', ({ cache, proxy }) => {19 cache({20 browser: {21 maxAgeSeconds: FAR_FUTURE_TTL,22 },23 edge: {24 maxAgeSeconds: FAR_FUTURE_TTL,25 staleWhileRevalidateSeconds: 60 * 60 * 24,26 },27 })28 return proxy('commerce')29 })30 ...31}
These routes are set up to cache the default API endpoints from SAP Commerce Cloud, but should be configured to suit your application as needed.
Finally, to configure prefetching for your pages, configure the routes that use SSR using the
prefetchUpstreamRequests: true
flag for the cache
function:JavaScript
1const CACHE_SSR_PAGE = {2 prefetchUpstreamRequests: true,3 edge: {4 maxAgeSeconds: PAGE_TTL * 365,5 staleWhileRevalidateSeconds: PAGE_TTL * 365,6 forcePrivateCaching: true,7 },8}910return new Router()11 ...12 .get('/base-site-path/:path*', ({ cache }) => {13 cache(CACHE_SSR_PAGE)14 })15}
Running Locally
Test your app with the Sites on your local machine by running the following command in your project’s root directory:
Bash
10 run
You can do a production build of your app and test it locally using:
Bash
10 build && 0 run --production
Setting
--production
runs your app exactly as it will be uploaded to the Edgio cloud using serverless-offline.Deploying
Deploy your app to the Sites by running the following command in your project’s root directory:
Bash
10 deploy