Pulling in Content from a Legacy Site

React Storefront supports the strangler pattern - gradually migrating a legacy site to a PWA by replacing one feature at a time. React Storefront allows you to bring in content from the legacy site into the PWA unchanged or modified/enhanced to improve speed and/or user experience using a transformation API based on Cheerio.

Project Configuration

To pull in content from a legacy site, edit the host_map in your moov_config-*.json files so that it points to the legacy site. In a single-domain project, you'll first need to create a DNS entry to point to the legacy site, and update the current DNS entry to point to the new PWA. We recommend using something like prod-origin.my-domain.com. In this case, your host_map should look like:

"host_map": [
  "www.my-domain.com => prod-origin.my-domain.com"
]

Proxying the Legacy Site for All Unmatched URLs

You can configure your app to display content from the upstream site whenever a route is unmatched by adding a fallback route with a proxyUpstream handler:

// src/routes.js

import { Router, proxyUpstream } from 'react-storefront/router'

new Router()
  // other routes defined here...
  .fallback(proxyUpstream())

When no arguments are provided to proxyUpstream(), the original page is returned unaltered. To transform the upstream page, specify a path to a handler:

// src/routes.js

import { Router, proxyUpstream } from 'react-storefront/router'

new Router()
  // other routes defined here...
  .fallback(proxyUpstream('./proxy/proxy-handler'))

The handler function takes params, request, and response and should return the transformed html by calling response.send(html):

export default function myProxyUpstreamHandler(params, request, response) {
  fns.init$(body)
  // add your custom transformation logic here...
  response.send($.html())
}

Here's an example from react-storefront-boilerplate which replaces the upstream site's header with the PWA's header:

// src/proxy/proxy-handler.js

import renderHeader from './renderHeader'
import getStats from 'react-storefront-stats'

export default async function proxyHandler(params, request, response) {
  try {
    const stats = await getStats()
    fns.init$(body)                 // parse the upstream response body using cheerio
    renderHeader(stats)             // reuse the PWA header in adapt pages
    response.send($.html())
  } catch (e) {
    response.send(e.stack)
  }
}
// src/proxy/renderHeader.js

import React from 'react'
import Header from '../header/Header'
import { render } from 'react-storefront/renderers'
import AppModel from '../AppModel'
import theme from '../theme'

/**
 * Inserts the PWA header into adapt pages.
 * @param {Object} stats Webpack build stats object for the client build
 */
export default function renderHeader(stats) {
  const { html } = render({
    component: <Header/>,
    state: createState(),
    theme,
    stats,
    clientChunk: 'header' // the name of the entry injected into config/web.dev.*.js
  })

  // remove the existing header
  $body.find('header').remove()

  // add the new header and supporting resources to the document
  const $header = $(tag('div', { class: 'mw-header' })).append(html)
  $body.find('#page-container').attr('id', null).prepend($header)
}

/**
 * Extracts a menu item from a nav menu element on www.moovweb.com.  The logic here is 
 * specific to www.moovweb.com and only serves an example of extracting MenuModel data from
 * the upstream site.
 * @return {AppModel}
 */
function createState() {
  function extractMenuItem() {
    const el = $(this)
    const link = el.children('a')
    const href = link.attr('href')
    const children = el.find('.sub-menu > .menu-item').map(extractMenuItem).get()
  
    return {
      text: link.text(),
      url: children.length ? null : href,
      items: children.length ? children : null
    }
  }

  return AppModel.create({ 
    menu:{
      levels: [{
        root: true,
        items: $body.find('#top-menu > .menu-item').map(extractMenuItem).get()
      }]
    }
  })
}

Transforming Specific Pages

You can also transform upstream content for specific URLs by declaring routes with a proxyUpstream handler:

// src/routes.js

import { Router, proxyUpstream } from 'react-storefront/router'

new Router()
  .get('/about', 
    proxyUpstream('./about/about-handler') // transform the about page using the function provided in src/about/about-handler
  )
  .fallback(
    proxyUpstream() // return all other unmatched pages unaltered
  )

Transformation API

The API for transforming upstream pages is documented here:

Moovweb API Docs