Setting up a Hugo static site inside Laravel on Forge
Making a hybrid static + dynamic site can be quite a useful combination. This might sound like an oxymoron, but it simply means serving static pages from a site that also runs a dynamic web application. You can get some of the best of both worlds with this approach.
My Chinese-learning site ChineseBoost uses this combination – a lot of the content pages, e.g. for Chinese grammar, are statically generated with Hugo, while dynamic parts of the site are served by a Laravel application. Similarly on our pop out cards shop, we have a static blog and an ecommerce application on the same server.
This is easy to achieve when you’re using a webserver like nginx. Your nginx
installation is probably set up by default to serve static files that it finds
in the public
directory with directives like this:
index index.html index.htm index.php;
location / {
try_files $uri $uri/ /index.html /index.php?$query_string;
}
The config file is usually at e.g. /etc/nginx/sites-available/foo.conf
.
This means that if you have a file at public/foobar-static-slug/index.html
,
nginx will serve that at www.yourdomain.com/foobar-static-slug/
. Nginx is fast
at everything, and especially fast at serving static files, so this will perform
nicely with minimal load.
Read more about serving static content with nginx.
Set up Hugo inside Laravel
You can have Hugo output your static content straight to the public
directory
where nginx will then serve it.
I would suggest having a separate (non-public) directory for your static
content, e.g. blog
at the top-level of your repo, which your static source
files live in. You could have a separate repo for this and pull it in to do the
static generation, but I prefer keeping related things in the same repo as much
as possible.
You have a standard Hugo set-up inside your blog
directory, with the important
config.yaml
file which controls how the site gets generated. There is nothing
different or special about how you set up this Hugo site, despite it being
inside a repo that is primarily for a Laravel application.
The approach I take is to use a symbolic link to the static files that Hugo
generates, at the path I want the static content to appear in the public
directory. For example, if you want your static content on /blog/
, you’d do
something like:
cd /home/whoever/yoursite.com/blog
yarn install # etc for blog setup...
if [ ! -f ./hugo ]; then
wget https://github.com/gohugoio/hugo/releases/download/v0.62.2/hugo_0.62.2_Linux-64bit.tar.gz
tar -vxzf hugo_0.62.2_Linux-64bit.tar.gz
fi
./hugo
ln -sf /home/whoever/yoursite.com/blog/publish /home/whoever/yoursite.com/public/blog
That can be run on a server during your deployment process.
Now the blog content is served on /blog/
.
Because of how nginx checks for the existence of static files, you can still
have Laravel routes that start with /blog/
, so long as there isn’t a real
index.html at that location on disk.
Automatically regenerating on deploy with Forge
This works quite nicely with Laravel Forge, as you can put the above script in the deploy script for a site, and Forge will then regenerate your static content each time you merge to master as part of its deployment process.
Your homepage can be static
Just a note that it can be beneficial to have a static HTML file as your
homepage by putting an index.html
file inside public
. The homepage often
gets a large proportion of traffic, and serving it statically reduces the load
to minimal levels. It also means your homepage is likely to stay up even if
something goes wrong with your Laravel application, which looks better than the
whole site going down.
If you’re regenerating the static content on each deploy (or more regularly, see below), you can still have regularly updating “live” content on the homepage even when it’s a static file.
If you don’t want to generate that file with Hugo, it’s quite easy to get
Laravel to write the content of your homepage Blade view file to
public/index.html
, so you can control that page from Laravel using your full
application, but have the benefits of it being static.
Scheduled Hugo regeneration with Laravel console command
Laravel makes it easy to run application commands and bash scripts on a schedule (one of my favourite features of Laravel), and you can utilise this to automatically regenerate your static content regularly, e.g. once an hour or whatever cadence suits your content:
<?php
$schedule->exec('(cd /home/whoever/yoursite.com/blog && ./hugo)')->hourly();
Note that that assumes the Hugo binary is present there and that the output files are linked from the public directory as described above.
Now the static content is kept up-to-date at all times. If your Laravel application or scheduler goes down, you will at least have the most recently generated static content being served, so the whole site won’t be down.
Laravel + Hugo data files is fun
One final thing that is quite useful is combining this with Hugo’s data templates.
Hugo can read from JSON or CSV files while it is generating the site, and it’s easy to have Laravel write these files in a scheduled command using your site’s dynamic data.
For example, Pop Robin Cards has product catalogue data from Laravel written into JSON files for Hugo to use when generating the static content. Because both get refreshed regularly, this stays up to date but keeps the benefits of managing static content.