Edgio
Edgio

Spartacus for SAP Commerce Cloud (formerly SAP Hybris)

This guide shows you how to deploy a Spartacus application to Edgio.

Connector

This framework has a connector developed for Edgio. See Connectors for more information.

System Requirements

Sign up for Edgio

Deploying requires an account on Edgio. Sign up here for free.

Install the Edgio CLI

If you have not already done so, install the Edgio CLI.

With npm:

Bash
1npm i -g @edgio/cli

With yarn:

Bash
1yarn global add @edgio/cli

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@9
2ng new my-edgio-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
1edgio init

This will automatically add all of the required dependencies and files to your project. These include:

  • The @edgio/core package
  • The @edgio/angular package
  • The @edgio/cli package
  • The @edgio/spartacus package
  • The @edgio/prefetch package
  • edgio.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 edgio.config.js

For an app called my-edgio-spartacus-app the Edgio config file created by edgio init will look like so:

JavaScript
1// This file was automatically added by edgio deploy.
2// You should commit this file to source control.
3
4module.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 edgio 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. @edgio/prefetch library will pick up on the upstream requests made by reading the x-0-upstream-requests response header. An example scenario:

  1. User A lands on /product/1.
  2. /product/1 has not been cached in the edge and thus will be server-side rendered.
  3. The rendering server has been modified to track upstream requests by patching https.request.
  4. The rendering server sets x-0-upstream-requests to, for example: /rest/v2/1;/rest/v2/2;
  5. The HTML response for /product/1 is now cached and for future requests served from the edge along with the x-0-upstream-requests response header.
  6. User B lands on a page that has a link to /product/1. /product/:path* has been configured with cache.browser.spa: true. Because of this configuration, @edgio/prefetch will know to make a prefetch HEAD request for /product/1, and only if product/1 can be served from the edge will it prefetch all requests specified in x-0-upstream-requests response header.
  7. 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'
4
5// edgio
6import * as http from 'http'
7import * as https from 'https'
8import createRenderCallback from '@edgio/spartacus/server/createRenderCallback'
9import installEdgioMiddleware from '@edgio/spartacus/server/installEdgioMiddleware'
10
11// Express server
12const server = express()
13
14installEdgioMiddleware({ server, http, https });
15
16const PORT = process.env.PORT || 4200
17const DIST_FOLDER = join(process.cwd(), 'dist/<your-project-name>')
18
19// * NOTE :: leave this as require() since this file is built Dynamically from webpack
20const {
21 AppServerModuleNgFactory,
22 LAZY_MODULE_MAP,
23 ngExpressEngine,
24 provideModuleMap,
25} = require('./dist/<your-project-name>-server/main')
26
27server.engine(
28 'html',
29 ngExpressEngine({
30 bootstrap: AppServerModuleNgFactory,
31 providers: [provideModuleMap(LAZY_MODULE_MAP)],
32 }),
33)
34
35server.set('view engine', 'html')
36server.set('views', DIST_FOLDER)
37
38server.get(
39 '*.*',
40 express.static(DIST_FOLDER, {
41 maxAge: '1y',
42 }),
43)
44
45// All regular routes use the Universal engine
46server.get('*', (req, res) => {
47 res.render(
48 'index',
49 { req },
50 createRenderCallback(res),
51 )
52})
53
54export 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 @edgio/angular will know to static serve the file.

Installing the service worker and any further prefetching will be handled by @edgio/prefetch by invoking the install function imported from @edgio/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'
4import install from '@edgio/prefetch/window/install'
5
6@Component({
7 selector: 'app-root',
8 templateUrl: './app.component.html',
9 styleUrls: ['./app.component.scss'],
10})
11export class AppComponent implements OnInit {
12 isBrowser: boolean
13 title = '<your-project-name>'
14
15 constructor(@Inject(PLATFORM_ID) platformId: Object) {
16 this.isBrowser = isPlatformBrowser(platformId)
17 }
18
19 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:

JavaScript
1pwa: {
2 enabled: environment.production
3 enabled: false
4},

You may also need to disable it in your app.module.ts file:

JavaScript
1ServiceWorkerModule.register(
2 'ngsw-worker.js',
3 {
4 enabled: environment.production,
5 enabled: false
6 }
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 edgio init sends all requests to Angular server via a fallback route.

JavaScript
1// This file was automatically added by edgio deploy.
2// You should commit this file to source control.
3
4import { Router } from '@edgio/core/router'
5import { angularRoutes } from '@edgio/angular'
6
7export default new Router()
8 // Prevent search engine bot(s) from indexing
9 // Read more on: https://docs.edg.io/guides/cookbook#blocking-search-engine-crawlers
10 .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 indexing
3 // Read more on: https://docs.edg.io/guides/cookbook#blocking-search-engine-crawlers
4 .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}
9
10return 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
1edgio run

You can do a production build of your app and test it locally using:

Bash
1edgio build && edgio 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
1edgio deploy