Hybrid Setup Combining SPA and Server Side Pages

This document describes how to achieve a hybrid approach in which a single channel serves both an SPA and normal server side website pages. Or a single channel that serves multiple SPAs. Or actually multiples SPAs (eg 2 Angular apps, 1 React app and 1 Vuejs app) and normal server side website pages.

Why the Hybrid Approach

If you already have a server-side-rendering-pages based website but you want to add an SPA to the existing website or vice versa, then you can apply the hybrid approach. Or if you have an SPA but you want to add another SPA, then you can apply the hybrid approach, too. Most importantly, you want to achieve the following goals by adopting the hybrid approach:

  1. As a content editor, you need to be able to work on documents, without having to think about whether the document is for a certain SPA or for server-side-rendering webpages. 
  2. As a content editor, you need to be able to create links between documents, regardless whether the document belongs to the same or a different SPA, focused only on document content which they're working on.

Hybrid Setup

The Hybrid Setup requires the same setup as described in Configurationexcept that you should not specify the SpaSitePipeline on mount level (hst:mount). Instead, you should configure the SpaSitePipeline on the SiteMapItems, which belong to the URL space of SPA. On SiteMapItems that belong to the same SPA, you need to configure the following property:


The property should be set to the same value for the @hst:applicationId for all the SiteMapItems that belong to the URL space of the same SPA. The purpose of the @hst:applicationId property is that the Page Model JSON API response will have the link type (internal or external), see Links: internal vs external section in Page Model JSON API. The @hst:applicationId property value is inherited by descending SiteMapItems unless redefined explicitly.

Note that if you serve your SPA from a node.js server or CDN, you do not need to specify the SpaSitePipeline on SiteMapItems for the hybrid approach, but you still need to configure @hst:applicationId property on SiteMapItems.

Hybrid Example

Suppose you have 2 SPAs and a set of normal server-side-rendering pages by HST. You have an SPA which is for job applications using job application documents maintained in the CMS and you have an SPA for satisfaction questionnaires. You also have 10,000 news articles which should be rendered as normal server-side-rendering pages as well. The following setup would support this kind of use cases.

              jcr:primaryType: hst:mount
              hst:pagemodelapi: resourceapi
              hst:mountpoint: /hst:hst/hst:sites/mysite
          hst:applicationId: jobApp
          hst:namedpipeline: SpaSitePipeline
          hst:relativecontentpath: jobs
            hst:relativecontentpath: ${parent}/${1}
          hst:applicationId: questionnaireApp
          hst:namedpipeline: SpaSitePipeline
          hst:relativecontentpath: questionnaires
            hst:relativecontentpath: ${parent}/${1}
           hst:relativecontentpath: news
             hst:relativecontentpath: ${parent}/${1}

The above example is pretty much a normal HST configuration, except that:

  1. The hst:root mount has @hst:pagemodelapi = resourceapi, which enables Page Model JSON API to be available at http://www.example.com/resourceapi.
  2. The SiteMapItems for jobs and questionnaires have @hst:namedpipeline = "SpaSitePipeline", indicating that when http://www.example.com/jobs or http://www.example.com/questionnaires is requested for instance, only the root HST component and its render templates (ftl or jsp) are invoked. Also note that when http://www.example.com/resourceapi/jobs or http://www.example.com/resourceapi/questionnaires is invoked, the entire HST component hierarchy is invoked and serialized to JSON by Page Model JSON API.
  3. The jobs SiteMapItem contains @hst:applicationId = jobApp, ensuring that the SiteMapItems, jobs and jobs/_any_, belong to the SPA, jobApp.
  4. The questionnaires SiteMapItem contains @hst:applicationId = questionnaireApp, ensuring that the SiteMapItems, questionnaires and questionnaires/_any_, belong to the SPA, questionnaireApp.
  5. The news SiteMapItem does not contain @hst:namedpipeline = SpaSitePipeline, making it a set of normal server-side-rendering pages.

The nice thing about this setup, is that if a content editor creates links between different documents in the jobs folder, then the Page Model JSON API will show those links marked with internal like the following if the jobApp SPA is rendering its page:

"type": "internal"

This means the SPA can choose to make it a XHR request.

If the content editor, however, creates a link between a job document and a questionnaire document, the link to that questionnaire document in the Page Model API will be marked with external like the following if the jobApp SPA is rendering its page:

type = external

This indicates the SPA cannot fetch the resource as an XHR request, but it requires a full document request. Same goes when the link would be to a news item.

The above logic also applies if a Channel webmaster is working in the jobApp part in the Channel Manager by dropping in a component that shows, say, the last three questionnaire documents. These questionnaire documents cannot be displayed within the jobApp SPA, again resulting the Page Model API with mark them to be external links.

Prototype Pages for SPA

On a prototype page you can specify for which SPA the prototype page is meant. This means that when adding a new page in the Channel Manager, it automatically gets the @hst:applicationId property specified on the prototype page, resulting that the new page can be part of the specific SPA. For example by specifying the following example on the prototype page (that has the mixin type, hst:prototypemeta), all the pages added using this prototype will be part of the jobApp SPA.

hst:applicationId = jobApp


Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?