Custom 404 page for a static website

Problem

At the moment, Platform.sh does not have the ability to define custom error pages. For a normal web application, you would generally already handle this in the app. But for a static website, this is not the case.

Solution

There is a way you can make it work, but it’s considered a hack. This is not officially supported but is documented here to be used as an example of what is possible.

Assumptions

  • You have an existing project set up and know how to tweak your .platform.app.yaml file
  • It assumes you are running a PHP container, or know how to tweak the proposed script to your preferred language.

Setup

We will be using the passthru element in web-locations.

From https://docs.platform.sh/configuration/app/web.html#locations

passthru: Whether to forward disallowed and missing resources from this location to the application and can be true, false or an absolute URI path (with leading /). The default value is false. For non-PHP applications it will generally be just true or false. In a PHP application this will typically be the front controller such as /index.php or /app.php. This entry works similar to mod_rewrite under Apache. Note: If the value of passthru does not begin with the same value as the location key it is under, the passthru may evaluate to another entry. That may be useful when you want different cache settings for different paths, for instance, but want missing files in all of them to map back to the same front controller. See the example block below.

What the above means is that we can define a passthru for any file that does not exist (i.e.: is not a static file, or is non existent), and we can then pipe all requests to a special PHP file that shows a 404.

Create a special PHP file.

Create a file called custom_404.php and put it in the root of your project. This script will be called by passthru. It can contain anything you want, but make sure it responds with a 404 Not Found header. This makes sure that search engine bots know that certain pages don’t exist.

Example custom_404.php:

<?php
header("HTTP/1.0 404 Not Found"); //IMPORTANT: we need to return the correct HTTP status code.
?>
<html>
  <head>
    <style>
        /* simple styling to center the text vertically and horizontally */
        html, body, .container {
            height: 100%;
        }
        .container {
            display: -webkit-flexbox;
            display: -ms-flexbox;
            display: -webkit-flex;
            display: flex;
            -webkit-flex-align: center;
            -ms-flex-align: center;
            -webkit-align-items: center;
            align-items: center;
            justify-content: center;
        }
    </style>
  </head>
  <body>
    <div class="container"> 
      <p>Hi there, it looks like we can't find the page you are looking.</p>
      <p class="suggestions">
        <?php
        /*
        Add logic here that uses the PHP $_SERVER variable 
        to determine the user intent and display some suggestions
        $_SERVER['QUERY_STRING']; 
        */
        ?>
      </p>
      <p>Want to <a href="/">go back to the home page?</a></p>
    </div>
  </body>
</html>

Point passthru to custom_404.php

Now open up .platform.app.yaml and edit your web.locations so that the passthru points to the correct location.

web:
    locations:
        '/':
            # The public directory of the application relative to its root.
            root: 'public'
            index: ['index.html']
            scripts: false
            allow: true
            expires: 1d
            passthru: "/custom_404.php"

Note: Make sure you remove the web.commands.start from your configuration, if it’s still there the php script cannot run.

1 Like

Just to add a note about doing this within a NodeJS environment: I was able to get a custom 404 handler working by:

  1. Create a 404.js handler with a wildcard route match - I used ExpressJS, YMMV.
const config = require("platformsh-config").config();
const express = require('express')
const app = express()
app.get('/*', (req, res) => {
    res.status(404).send('Sorry, that page cannot be found.')
})
app.listen(config.port, () => {
    console.log(`[INFO]: ExpressJS listening on ${config.port}.`)
})
  1. Alter app config in .platform.app.yaml to run the 404.js handler script by default, and set passthru to true, to forward any not-found requests to it:
web:
    commands:
      start: node 404.js
    locations:
      '/':
        root: 'build'
        index: ['index.html']
        scripts: false
        allow: true
        passthru: true

Hope that helps!