Hosting Intercom Pages under a Subfolder

Sun, 29th Mar 2020
< More Articles

Intercom allow you to host your FAQs and Support Articles on a subdomain, but this isn't always great for SEO.

Normally you'd write a simple rewrite rule in your webserver to forward requests to Intercom, but for some reason, Intercom have blocked this, which makes life difficult. Using a little serverless magic, I show you how to build a simple, quick proxy to map your own subfolder (example.com/support) to your intercom support site.

First of all lets talk about the stack:

  • Hosting: Zeit Now . It's pretty good at Serverless.
  • Http Server: Hapda (My own creation)
  • Proxy: h2o2 - A simple proxy handler for HapiJS

First we create a `now` project. This is as simple as installing now and creating a new npm module:

npm i -g now
npm init

That's it. But the project doesn't do anything (yet).

Now will automatically create API routes if you create JavaScript files inside `api`. So lets leverage that.

mkdir api
touch api/proxy/index.js

Ok, done. Now lets look at what this file should contain:

'use strict'

const { hapda } = require('hapda')
const h2o2 = require('@hapi/h2o2')
const wreck = require('@hapi/wreck')

const route = {
  method: 'GET',
  path: '/support/{param*}',
  handler: {
    proxy: {
      mapUri: function (request) {
        return {
          uri: `https://intercom.help/beyonk/${request.params.param}${request.url.search || ''}`
        }
      },
      onResponse: async function (err, res, request, h) {
        const payload = await wreck.read(res, {})
        return h.response(payload.toString().replace(/\/beyonk\//g, '/support/'))
      }
    }
  }
}

async function bootstrap (server) {
  await server.register({
    plugin: h2o2,
    options: {
      redirects: 10,
      ttl: 'upstream'
    }
  })
}

module.exports = hapda(route, bootstrap)

First we import three modules:

Hapda (for attaching a tiny Hapi handler to the exported serverless route - I hate express/I don't see the point of fumbling around with raw handlers, so I just use Hapi as I would when I'm not using serverless. Hapda allows me to use all of Hapi's power with no overheads.

h2o2 for proxying. This is a core Hapi module and makes proxying a request a matter of some simple configuration.

wreck another Hapi module, which is used to parse and modify http payloads. We need to do a bit of url rewriting for the response from Intercom (as they won't know where we're proxying to), to ensure the links continue to work.

That's it!

Lets talk about the different parts of the project

Route

The route handler is how Hapi receives requests. We use a wildcard param to absorb the full URL given to us, and we add the h2o2 proxy handler configuration as our handler. We can handle the whole thing via config.

mapUri

We can't use onResponse with the regular `uri` config of h2o2 so we will rebuild the URL ourselves. No problem.

onResponse

We take the response from intercom.help, and we chop out our hosted name and replace it with the path to our proxy. This means our urls will appear in the Intercom pages as /support/something/or/other rather than /hosted-name/something/or/other.

bootstrap

Hapda lets us play with the server before we bootstrap it. We need this to register the h2o2 plugin, and set some options - the ttl (just takes the upstream caching info), and the max number of redirects we will allow before giving up.

That's it! We export our Hapda handler, and we're done here.

Now configuration

Because we want our urls to sit at /support rather than /api/proxy, we use a now rewrite to ensure this happens.

In now.json in the root of our project:

{
  "functions": {
    "api/proxy/index.js": {
      "memory": 128,
      "maxDuration": 60
    }
  },
  "rewrites": [
    { "source": "/support/(.*)", "destination": "/api/proxy" }
  ]
}

You'll also notice functions configuration. I don't need 3gb of memory to load a Hapi handler. I don't even need 128mb (more like 16-32mb), but lambda scales CPU along with memory, so we don't want to drop too low or things might get slow.

The rewrites syntax is a bit different to how the docs show it to be - we capture the full path after /support using `(.*)` but we don't pass it to the API handler with $1, otherwise it chops all the segments off. I don't know why, but that's how it works. It took me far too long to figure this out, so I'm saying it here.

We're done

You can test your project locally, and then deploy it to `now`:

now dev

# now you can try it out locally.

now --prod

# and now it's live. enjoy!