Prepending PHP to a Page in Gatsby

Abstract image used to represent Prepending PHP to a Page in Gatsby
Image by Ben.

It goes against the entire Jamstack philosophy to add reliance to a backend technology like PHP, and certainly the Gatsby Content Mesh advocates against it, but sometimes there's simply no alternative.

Often, these serverside scripts can be set up as standalone endpoints which you can then call via HTTP (for example by using axios). This is a really common approach for oneway communication, like submitting a contact form. In Gatsby, if you are confident that your eventual hosting environment supports PHP (for example) you can even add these files into your static folder, safe in the knowledge that they will then be placed in, and be available at, the root of your domain.

However, there are times when this isn't sufficient. A recent project I worked on required that every page load be hydrated with uptotheminute live pricing data, acquired from a monolith system built in PHP, where security policies made it impossible to use a more familiar, distributed approach. You might argue: possibly not the best use of a static site generator!

Appending PHP functionality to a Gatsby template allows us to fetch up-to-the-minute prices and hydrate the data on-screen.

So, the answer in this instance was a PHP include, prepended to each generated page at build time. It produced an object on window for the React components to read when the page loaded.

Another use for me has been to prepend a small piece of PHP to my error page. This lets me record errors on the server and respond to patterns in those requests, including requests from visitors or bots that do not run JavaScript.

This workaround assumes Apache hosting where .htaccess overrides are enabled and the configured PHP handler accepts the mapping below. PHPFPM and other server setups may require different handler configuration, so check that with the host first. A purely static host cannot execute this PHP.


Use .htaccess to Allow PHP Processing

The html files generated by Gatsby are not on their own going to trigger any PHP you attempt to push into them without a little help.

If you don't already have an htaccess file in your project, create one (important: the file name is .htaccess there is nothing before the extension, your OS is probably going to hide it immediately). This goes into your static folder and will then be copied into your site root on build.

Then, there are two options in the code block below to use as best suits your needs:

  • The first will mean that all files with the .html extension should be parsed as PHP. I don't recommend this unless you really and genuinely are relying on the backend for every page (in which case, maybe Gatsby isn't the answer).
  • The second does the same but only applies to a specific file in this case 404.html.
AddType application/x-httpd-php .html

<Files 404.html>
  AddType application/x-httpd-php .html
</Files>

Install prepend-file

prepend-file is a simple package which just prepends text to a file within your generated site. We will use this as part of our deployment script. So, add to your project by simply running yarn add prepend-file from the project root (unless you're using NPM, in which case it's npm install prepend-file).


Write Your Prepend Script

This is a very similar process as I've described before where we want to add additional Node.js functionality within our build process. Create a file called prepend.js in your folder root, import prepend-file, and configure it to add an include to your PHP file to the file you want to add PHP to.

This is how I capture hits on my error page (with some comments to explain what's going on). Note that I'm also using my LIVE_DEPLOY environment variable here to determine where the build is occurring.

// import our environment variables
require('dotenv').config({ path: '.env' });

// if our LIVE_DEPLOY variable isn't set, or is not set
// to 'true', then we return without going any further
if (process.env.LIVE_DEPLOY !== 'true') {
  console.warn('Deploy env is false, not prepending');
  return;
}

// import prepend-file
var prependFile = require('prepend-file');

// open the 404 file, and inject an include for
// include.php
prependFile('./public/404.html', "<?php include_once('include.php'); ?>", (error) => {
  if (error) {
    console.error('Could not prepend the error file:', error);
    process.exitCode = 1;
    return;
  }

  console.log('Error file prepended successfully');
});

The script opens the generated public/404.html and prepends this PHP statement:

<?php include_once('include.php'); ?>

Put include.php in the static folder so it is copied into the deployed output. The include statement sits at the top of 404.html; the PHP handler executes it when serving the request. The full <?php opening tag avoids relying on the host enabling short tags.


Add Prepend to Your Build Script

Much like setting up Gatsby to deploy via FTP, one final task is to add your new prepend script into your build process and much of this will come down to your own personal preference.

Inside my package.json, I like to have a specific 'deploy' script which is only called (via yarn deploy) within the pipeline I have configured to push content out onto the live site. Mine looks like this:

"scripts": {
  "deploy": "gatsby clean && gatsby build && node prepend && node deploy"
}

In order, this chains four commands together:

  1. gatsby clean make sure we have cleared out any cached items we don't need;
  2. gatsby build build the Gatsby project;
  3. node prepend calls our new prepend.js file which in turn adds a PHP include to our 404.html file;
  4. node deploy calls deploy.js which takes a copy of the contents of the public folder, and uploads it via FTP. I've written about this here.

To try the script locally, run node prepend against an existing generated file in public, with LIVE_DEPLOY set to true. Check the file starts with the include. If prepending fails, the script reports the error and exits with a nonzero status, so the && chain stops before node deploy.

Postscript

June 2026: this was a pragmatic workaround for a Gatsby site tied to a PHPbacked estate. I'm keeping it because that kind of compromise still appears in migrations, but for new work I would usually separate the integration properly or use the issue as a signal for GatsbytoNext.js migration planning.

Want to find out more?

If you need senior handson support with a complex React or Next.js platform, migration, performance issue, or technical SEO problem, send me the context and I'll tell you where I can help.