Deploying a Statamic v2 site to Netlify

September 16th, 2019

My personal website & blog has been hosted on a DigitalOcean droplet for years, but recently I've been trying to move away from hosting and managing my own server.

Looking for solutions for this problem I came across Netlify, a PaaS that allows you to host static sites for free. Since this site has always been fairly static without any forms or dynamic content, this would be a perfect solution.

Why Statamic?

I'm pretty much in love with Statamic. It has one of the best developer experiences I've had with a CMS, making it very easy to define sections, fieldsets, templates... without ever having to leave your code editor. This combined with the beautiful and amazing writing experience, it's just a no-brainer for me to keep using it.

hero.jpg
The Statamic author experience

But.. Statamic is a dynamic PHP application, which can't be hosted straight on Netlify 🤔

Full measure static caching to the rescue

Statamic has a great full measure static caching system, which allows you to define a folder where the static files are stored. This works when the pages are visited from the browser. 

My first thought was to use our Spatie Crawler package to crawl the pages so they would be stored in the static html files, and then deploying that to Netlify, but this was slow and needed a web server to be used, so it couldn't be run directly in Netlify's build system.

Statamic Kernel

Statamic has an HTTP Kernel which handles all its requests, as you can see in the code of the index.php file, this is also how Laravel handles its requests.

$kernel = $app->make('Illuminate\Contracts\Http\Kernel');

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);

Using this snippet as inspiration, I could collect all of Statamic's content, and run it through the kernel to get static pages.

Build script

The first step is getting all the content we want to statically cache, luckily Statamic makes this very easy with its Content api.

$requests = Content::all()
  ->map(function ($content) {
    if ($content->url()) {
      return \Illuminate\Http\Request::create($content->url(), 'GET');
    }

    return null;
  })
  ->filter()
  ->values()
  ->unique()
  ->toArray();

From each content item, we create a new Laravel Request if it has a URL. Then we make sure we only get the unique items as we don't want to make more requests than necessary.

After this, we can have Statamic's kernel handle all the requests

$kernel = app(\Statamic\Http\Kernel::class);

foreach ($requests as $request) {
  $response = $kernel->handle($request);
  $kernel->terminate($request, $response);
}

We need to call the ->terminate($request, $response) function on the Kernel, as Statamic handles its static caching in this method. Another way we could solve this is to save the response into html files ourselves, but I've chosen to let Statamic handle this.

Not quite there

Once all requests are handled, you'll notice Statamic saves the homepage as _.html and your pages as page_.html, Netlify however wants an index.html, and no underscores in the other filenames, so we'll have to run a quick rename on all the files.

/** @var \SplFileInfo[] $files */
$files = File::allFiles(webroot_path('/public'));
$files = array_filter($files, function (\SplFileInfo $file) {
  return $file->getExtension() === 'html';
});

foreach ($files as $file) {
  $filename = $file->getFilename() === '_.html'
    ? str_replace('_.html', 'index.html', $file->getRealPath())
    : str_replace('_.html', '.html', $file->getRealPath());
  
  File::move($file->getRealPath(), $filename);
}

I store my files in the /public folder, this is also where my assets get built and images get stored, so I can tell Netlify to publish only this directory.

Getting Netlify to build your Statamic site

Netlify has a simple way to define its build commands, you can do this through their UI or by using a netlify.toml file in the root of your project. This is the configuration I'm using:

[build]
command = "yarn && yarn production && php artisan build"
publish = "public"

[context.production.environment]
PHP_VERSION="7.2"

I've created a small Addon for Statamic in my project that has a build command. So Netlify is instructed to just build my assets and then run the Statamic build script. Netlify uses PHP 5.6 by default, which is way too low, so we have to specify we want to use PHP 7.2 explicitly in the environment.

Now I can edit my blog and site locally, commit it to my repository and Netlify builds and distributes it globally in 30 seconds.

I've open sourced the code of my website here: https://github.com/riasvdv/rias.be

MENU