Serve Your Hugo Blog Through Laravel

Today building your blog is easy. You can choose from a lot of platform like WordPress, Medium, Ghost or you can use any static site generator.

We have a small new project where we needed a blog, and we needed it as fast as possible. Usually, we use WordPress because this is our tool of the trade when we need a CMS. Until now. The project running on Laravel so we should create a simple list section but for a more robust solution this path was impassable. And this is where Hugo and the static site generators came in.

The static generators give you and us the possibilities to make a complex sitein this case, a blogwith all of its features like taxonomies, metadata, theme handling. Besides this, the generated sites will be statics so these sites will be served amazingly fast and make less load on the server. So it is a good choice if you want something to build fast, but you also want to create on the top of performance.

These tools are viral nowadays because of the modern and separated architecture. There also a new stack named JAMstack which is based on client-side JavaScript, reusable APIs, and prebuilt Markup.

We Chose Hugo

Choosing a generator is not so easy because there a lot of good choices. Our choice was the Go based Hugo. We know a bit about these tools but never used one of our projects, so it was the time. Hugo is blazing fast when it is generating your project files, using it is smooth and practical. The documentation and the support are well-edited and high quality.

Using Hugo, you also get:

  • Flexible content management. You can have unlimited content types, taxonomies, menus.
  • You can also make it dynamic through API-driven content.
  • An obvious templating system which will be familiar if you are a theme designer.
  • Multilanguage support if needed.
  • Create shortcodes like in WordPress.

Our first build was simple but do the job which is a blog with generic functionalities. The design was given, and the development was around 10 hours. This experience is showed us that it is a neat and handy system which served us well. Of course, our code currently not the best and there is room for the improvements.

Serving It Through Laravel

As we mentioned, the system runs on Laravel, so we need to serve our static blog through it. Of course, we could just modify the .htaccess a little a bit and put the generated content to the public directory, that’s all. We don’t have to involve Laravel at all, but then why?

We want to have the flexibility that Laravel offers. Of course, no data will come from the back-end, but we can have middlewares, 404 handling and much more. Let’s see the steps of the integration.

Placing the Hugo Project

So, when your Hugo project got compiled, you can find a public directory in the root. That is what we need. Place it to your Laravel app’s public directory and rename it how you want. We named the directory to blog. It contains all the HTML, CSS and JS files with some extra like a sitemap and so on.

So to make it clear, now we can reach the Hugo project in the public/blog directory. Since it’s in public, all the CSS and JS files we use in the HTML files will work.  Also, another thing we might recognize is that Hugo builds a structure that follows a nice URL structure. For example, the category/seo URL’s content would be at public/blog/category/seo.html. That means, we can easily track the files based on the given slug. Neat!

Setting up the Routes

We need to be a bit tricky how we define the routes. If we hit the /blog route, we want to include the blog/index.html file. Or if we hit the blog/category/seo route, we want to include the blog/category/seo.html file. So what is after the blog prefix, we want to handle it as one piece, but also it’s not required to provide any parameter.

Route::get('blog/{slug?}', 'BlogController')->where('slug', '(.*)');

So, now we can hit anything that starts with “blog”, we will call the BlogController. With the where() method, we also provide a regular expression, that makes sure, we handle the whole slug after the blog prefix as one parameter.

The Controller

Now we arrived at the vital component, the BlogController. As we know, we can create the controller via the artisan command. Take a look, then we give the explanations.

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\File;

class BlogController extends Controller
{
    /**
     * The path to the blog.
     *
     * @var string
     */
    protected $path;

    /**
     * Initialize a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->path = public_path('blog');
    }

    /**
     * Show a blog page.
     *
     * @param  string|null  $slug
     * @return \Illuminate\Http\Response
     */
    public function __invoke($slug = null)
    {
        $slug = rtrim($slug, '/');

        if (File::isDirectory($this->path.$slug) && File::exists($path = "{$this->path}{$slug}/index.html")) {
            return File::get($path);
        } else if (File::exists($path = "{$this->path}{$slug}.html")) {
            return File::get($path);
        }

        return response()->view('errors.404', [], 404);
    }
}
Note, we use __invoke() because we did not define any method at the blog route.

First of all, we define the $path to the blog directory. It makes a bit cleaner than to use all the time the public_path() helper. Then we make sure, to remove the trailing slash because the route blog/seo and blog/seo/ should be the same.

Then, we check if the given slug refers to a directory or not. If we call the blog/category route, it relates to the list of all the categories, so we automatically include the index.html of the directory.

If it’s not a directory, we check if is there any HTML file with the given path. If yes, we include the file, otherwise, we return Laravel’s 404 page. Of course, if you want a static 404, you can replace the last line.

Automatic Blog Deployment

So manually, replace the blog directory’s content can be a little inefficient. What we did to avoid this, was nothing but add the public/blog directory to the .gitignore and exclude it from the version control.

We created its repository, and we created an automatic deployment system for the blog. We don’t give details now, the point is, we automatized the process, instead of manually replacing it every time.

Summary

For us, it was essential to separate the blog from the core application. Also, we wanted to use something integrative solution, which saves us time and does not divide our energy.

We found this combination perfect. We have our Laravel app, and we have a static Hugo blog, that we serve via Laravel. We separate the content but still, we can use anything that the framework offers.