Stand With Ukraine. Stop Putin. Stop War.

After countless hours of copying and updating old content, I switched over the modmore support site to a new system last weekend. While the old one was running in MODX (FaqMan plus some custom routing), the new support site is powered by HelpScout Docs.

Docs is integrated with the HelpScout helpdesk we've provided support with for many years, so using that to host the FAQs makes sense. It allows the help widget ("Beacon") on the site to search through the FAQs, and also gives us quick access to all FAQs when responding to customer emails to point them in the right direction. 

Unfortunately shortly after making the switch, I ran into 2 compounding issues.

Problem 1: the built-in 404 page just plain sucks. On top of lacking branding, it does not offer the user any way to continue to find what they're looking for with a search or basic navigation. No search, no navigation, not even a link to the homepage.

HelpScout Docs' very plain page-not-found error

Problem 2: it would not accept a bunch of redirects. While Docs supports adding redirects through the back-end interface, it would not accept original URLs with a "+" or "(" or ")" in the path. While these are valid characters in URLs (whether plainly or percent encoded), Docs did not agree.

Unfortunately about half or the URLs on the original support site used some special character. 

Which means problem 1 + problem 2 = half of the users following existing links to the FAQs, end up on a 404 page with no clue where to look next. Ouch.

While the HelpScout team confirmed the problems, they also indicated they would not be fixed until a future big revamp, so I've had to figure out a way to deal with this. 

  • There's a good API for Docs which I could use to build a custom support site again, while feeding off the data stored with HelpScout, but the point of moving to Docs was so I didn't have to maintain or host the support site.
  • I could flip back the DNS to the old support site, but that would actually make the situation worse: I'd just gone through all the content and rewritten parts of it, which was only in Docs. I'd need to spend even more time copying that back into MODX if I were to go this route.

Finally I had a random thought while walking the dog: the entire modmore site is behind CloudFlare. CloudFlare has this thing called CloudFlare Workers, which interacts with a request "on the edge" and can be used to rewrite responses on the fly with JavaScript.

With CloudFlare Workers, I could fix both problems: apply the missing redirects, and present a different 404 page.

I had never used Workers before, but the gist is pretty easy: register a JavaScript function that takes in the fetch request, do stuff with it before and/or after it hits the origin server, and return a response. The function then gets applied to a route (in this case, the entire support subdomain) to make the magic happen.

In less than an hour I adapted the official example for applying bulk redirects, and this tutorial by Mickael Vieira for custom 404 pages with Workers, to fix both problems. Plus the free Workers tier is more than enough for our traffic, so I'm more than pleased with this quick solution.

The final worker code (with some minor tweaks) is included below if anyone has similar needs and is looking for inspiration:

// Redirects we can't configure for HelpScout docs from old path > new URL
const redirectMap = new Map([
  ["/faq/14-development+licenses", "https://support.modmore.com/category/119-free-development-licenses"],
  // ... add more redirects here ...
]);

// This makes us interact with the request
addEventListener("fetch", event => {
  event.respondWith(handleRequest(event.request));
});

// The magic function that handles the request to apply our redirects and 404 handling.
async function handleRequest(request) {
  const url = new URL(request.url);
  const path = url.pathname;

  // Check if we have a redirect on-file we need to process before handing off the request to the origin
  const redirect = redirectMap.get(path);
  if (redirect) {
    return Response.redirect(redirect, 301);
  }

  // Send the request to the origin (helpscout) server
  const response = await fetch(url.toString(), request);

  // If we reach a html 404 page, replace it with our own 
  if (response.status === 404 && isHTMLContentTypeAccepted(request)) {
    return fetchAndStreamNotFoundPage(response);
  }

  // Return the origin response
  return response;
}

// Function that streams a 404 page 
// See https://www.mickaelvieira.com/blog/2020/01/27/custom-404-page-with-cloudflare-workers.html
async function fetchAndStreamNotFoundPage(resp) {
  const { status, statusText } = resp;
  const { readable, writable } = new TransformStream();

  // This could in the future point to a dedicated 404 page, but for now I'm okay with showing the homepage
  const response = await fetch("https://support.modmore.com/?err=404");
  const { headers } = response;

  response.body.pipeTo(writable);

  return new Response(readable, {
    status,
    statusText,
    headers
  });
}

// Makes sure only text/html requests are handled. 
function isHTMLContentTypeAccepted(request) {
  const acceptHeader = request.headers.get("Accept");
  return (
    typeof acceptHeader === "string" && acceptHeader.indexOf("text/html") >= 0
  );
}

I'm still a big fan of HelpScout's helpdesk solution. Over the years I've gotten to know them as very detail- and customer-oriented in their services, so hopefully the big Docs revamp they're planning brings that attitude to the hosted knowledge base as well.

Until that happens, I'm very happy that CloudFlare Workers saved the day and helped me avoid having to do a lot of extra work!

Get the conversation going, and post your thoughts in a reply.

Comments are closed :(

While I would prefer to keep comments open indefinitely, the amount of spam that old articles attract is becoming a strain to keep up with and I can't always answer questions about ancient blog postings. If you have valuable feedback or important questions, please feel free to get in touch.