Use Laravel as a Static Site Generator and host it serverless

• 3 min read

In May this year, I've released a new side project: Laravel Download Statistics.

The idea for this project started while talking with a colleague about which Laravel versions a new or existing package should support. My answer to this question back then was: "I guess it should support the versions which are currently most popular".

But how do you determine, which versions are the most popular? I thought the Packagist statistics are a good start, but without a "group by minor version"-feature you can't really see the popularitiy of a version. You would have to sum up all patch versions by hand and tally them in a spreadsheet.

Screenshot of the packagist.org statistics page for the Laravel Framework
Screenshot of the packagist.org statistics page for the Laravel Framework

However, through inspecting the source of packagist.org, I found their JSON API and quickly built a Laravel app which collects all download numbers of all Laravel versions through an Artisan console command.

Screenshot of Laravel Download Statistics for July 2019
Screenshot of Laravel Download Statistics for July 2019

Sweet! Now I have all the data in a SQLite-database and can run queries to group download numbers by date or version.

For now, I've built some simple views. One view to showcase the history of downloads for a single version. The other view summaries a single month and highlights, which version had the most downloads. (You obviously could create much more detailed views, but I think these are enough for now.)

Originally, I've deployed this Laravel app through Forge on a DigitialOcean droplet. However, the content of the site is static: Download numbers are only updated once a month. So … can I convert this app into a static site bundle which only contains HTML-files?
Yes I can! The crew at Spatie recently released laravel-export. The package crawls your app and stores the returned views as HTML files.

I've set up the app to store all HTML-files in a dist-folder, which is being committed to the repository. Makes deployment easier (don't @ me).

Instead of using a DigitalOcean droplet to host the HTML-files, I've switched to Netlify. I added the GitHub repository, set the publish directory to dist and that's it.

The Laravel app is now basically hosted on a serverless CDN infrastructure 🤯.

The last problem I needed to solve, was automating the process of updating the datasets. Without a server, I don't have access to the scheduler anymore, which could trigger the console command each month.

Luckily, GitHub has a solution for me: Scheduled GitHub Actions. Thanks to the knowledge I gained of writting a recent blog post, I could develop a simple GitHub Action that does all the work for me.

workflow "New Monthly Build" {
  on = "schedule(15 1 1 * *)"
  resolves = [
    "auto-commit-monthly-build"
  ]
}

# Install composer dependencies
action "composer install" {
  uses = "MilesChou/composer-action@master"
  args = "install --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist"
}

action "update-data" {
  needs = ["composer install"]
  uses = "./actions/update-data/"
}

action "auto-commit-monthly-build" {
  needs = ["update-data"]
  uses = "stefanzweifel/git-auto-commit-action@v1.0.0"
  secrets = ["GITHUB_TOKEN"]
  env = {
    COMMIT_MESSAGE = "Update Data Set"
    COMMIT_AUTHOR_EMAIL  = "foo@example.org"
    COMMIT_AUTHOR_NAME = "Stefan Zweifel"
  }
}

At the beginning of each month a new build is triggered, which:

  • clones the repository
  • installs the composer dependencies
  • runs the update console command
  • generates a new static build
  • committs all changes and pushes the changes back to GitHub

Netlify then automatically deploys the changes to their CDN. 👌

I really love this setup. Thanks to Netlify and GitHub Actions, I don't have to worry about servers or updating the datasets anymore.
Also new project ideas or use-cases for this setup came to mind. (For example, you could convert a read-only API to JSON files and deploy those files to the Netlify CDN…)

I also changed my opinion on the original question: Which Laravel versions should new packages support?
New answer: Just support the latest version. The adoption rate of new Laravel versions is getting better and better with each relase. It also doesn't make much sense to muddle your package code with if-conditions to check, if the installed version supports a specific helper or framework feature.

Also for existing packages my goto rule now is to drop support for older versions relatively quickly. Supporting multiple versions in a package can be a hassle. Removing support keeps the codebase clean and tidy (Laravel 5.5 also has been release 2 years ago!).

Upgrading Laravel to the latest version is also no longer a big deal, thanks to services like Laravel Shift.