Skip to main content Accessibility Feedback

PHP Islands Architecture

In JavaScript, Islands Architecture refers to an approach to web development where you serve up mostly static or server-rendered HTML, with small little islands of JS-rendered functionality only where needed.

This was, until last week, how the Lean Web Club was built.

Most of the site HTML was generated with a static site generator (SSG), and the interactive bits—like the buttons to bookmark stuff—were generated with and powered by JavaScript after the page loaded.

But with my recent rebuild, I did something both awesome and terrible, and I’m calling it PHP Islands Architecture.

Let’s dig in!

How it works

The tl;dr: the site is nearly entirely static, pre-rendered HTML. Rather than using JavaScript to render the interactive and dynamic bits, I use islands of server-powered PHP.

Let’s talk details.

Static site generators rely on the fact that if you point a browser to a server directory that has an index.html file in it, the browser will load that index.html file without needing it to be in the URL path.

These URLs both point to the same file…

  • https://gomakethings.com/about/
  • https://gomakethings.com/about/index.html

But, you can do the same thing with an index.php file, and send a file that runs PHP instead of just HTML.

On some servers, you might need to configure that behavior in an .htacess file. Here’s what mine looks like…

DirectoryIndex index.php index.html

I configured my static site generator to generate index.php files instead of index.html files.

This let’s me use the built-in templating system, write content in markdown, and take advantage of how server file systems work to automatically handle routing without needing to configure dynamic routing like WordPress does.

But it also means that I can add little islands of dynamic content that get rendered on the server instead of with JavaScript.

An example

In my files, I have some PHP at the very top of the page that checks if the user is logged in.

If they aren’t and the page is for members, I redirect to the homepage. If they are and the content is for logged-out visitors, I redirect to the logged-in dashboard.

A compiled index.php file might look a bit like this…

<?php

	// Get my helper functions
	require_once('/path/to/utilities.php');

	// These are generated by the SSG and front-matter
	$root_url = 'https://leanwebclub.com';
	$is_members_only = true;

	// A PHP function from utilities.php
	$is_logged_in = is_user_logged_in();

	// If only for members and user is NOT logged in
	if ($is_members_only && !$is_logged_in) {
		header('Location: ' . $root_url . '/login/');
		exit();
	}

	// If public and user IS logged in
	if (!$is_members_only && $is_user_logged_in) {
		header('Location: ' . $root_url . '/dashboard/');
		exit();
	}

	// Otherwise, they can view the content

?>
<!DOCTYPE html>
<html lang="en-us">
	<!-- ... -->
</html>

Within the HTML itself, I can also use PHP just for the parts where it’s needed…

<?php
	$fave_ids = get_user_favorites();
	$is_fave = in_array('content_id_1234', $fave_ids) ? 'true' : 'false';
?>

<button aria-pressed="<?php echo $is_fave; ?>">
	Bookmark
</button>

The button in the above example gets wrapped in a form that, by default, submits to a PHP backend, which updates the user settings and redirects back to the page.

When JS loads, it does the same thing, but with Ajax and no page reload.

Why do this instead of a different tool or just using JavaScript?

Am I abusing a tool in a way that it wasn’t intended? Yep!

Could I do something similar with NodeJS and Express and write the whole thing JavaScript? Also yes.

But, honestly, PHP is better.

It’s available on pretty much every server. I don’t have to restart it if the server reboots. I don’t have to generate bit chunks of HTML with JS (server rendered or not). I can just write HTML like HTML inside an HTML template or markdown file.

PHP is stable, well-documented, has a huge community around it, and frankly, just works.

My app loads fast AF because I’m not doing much with my PHP. But what I need it to do, it does supremely well!

Where doesn’t this work well?

If you have to make API calls to third parties to render content, you’re better off doing that in advance and caching it on the server (using a cron job), or using JS Islands Architecture for that.

API calls have to be done before you start returning any HTML, so it can create a lengthy “white screen with nothing on it” experience for users. Using JavaScript for those situations means the users gets something quickly, that’s updated with property content when it’s ready.

Doing this in Hugo

There were a few little quirks about making Hugo generate PHP, and I figured I’d share them here in case anyone else was interested.

First, you have to define a custom output format and media type for PHP in your config.yml file. Here’s what that looks like…

outputFormats:
  php:
    name: php
    mediaType: application/x-httpd-php
    isPlainText: true

mediaTypes:
  application/x-httpd-php:
    suffixes:
      - php

You also need to tell Hugo to use the php output format for everything it generates.

outputs:
  home: ["php"]
  section: ["php"]
  page: ["php"]

You can override this on a page-by-page basis if you want to generate a flat HTML file instead.

By default, Hugo will treat PHP in partials like weird, dangerous HTML, and encode it into a string. That’s for safety reasons, and it’s good.

You need to override that behavior when using partials by using the htmlUnescape and safeHTML functions.

{{ htmlUnescape (partial "php-bookmark-button.html" .) | safeHTML }}

Inside a shortcode, you need to wrap the whole thing in a string and call the safeHTML function on it

{{ "<?php echo 'Hello world'; ?>" | safeHTML }}

Surprisingly, it’s actually easiest inside markdown content files!

I created a php shortcode that runs the safeHTML function on whatever content you pass into it.

{{ .Inner | safeHTML }}

Inside markdown files, you just wrap any PHP in it and things work swimmingly.

<?php 
	$is_favorite = is_user_favorite(42); 
	if ($is_favorite) :
?>
<p>This is one of your favorites!</p>
<?php endif; ?>

Should you do this?

I dunno!

It’s worked out really well for me, and I plan to lean more heavily into it in the future. Your mileage may vary, but I would encourage you to give it a try!