Jigsaw is the preferred static site generator for Laravel developers. You can use Laravel's Blade templating engine to prepare your site's template and other front end stuffs. Jigsaw is powerful, customizable, easy to use and has no steep learning curve for Laravel developers. Other than the pros of Jigsaw, there are some short comings too. Like, there is no support for plugins, no default sitemap generation system etc.

Sitemap is a handy collection of pages for a site which helps in SEO too. Google Search Console asks for a sitemap.xml for a better execution of their web spider. I felt the necessity of having a dynamically generated sitemap for the Jigsaw site I have developed earlier. You can find an article from one of the Jigsaw creators on generating sitemap automatically. But, the article is bit old (the author mentioned it in the article too) and has some area of improvement. I am going to explain the full proof way of generating a sitemap using Jigsaw here. Let's dive in!

Let's Generate Sitemap

1. Install the composer package

For generating the sitemap, we need to install a package using this command (run this command from the root of your Jigsaw directory):

composer require samdark/sitemap

We will be generating our sitemap when the Jigsaw processor has prepared all the pages in the build_local directory. We can take advantage of Jigsaw's available event hook afterBuild. This event gets triggered when Jigsaw completes building the page from the source directory. We will generate the sitemap exactly after this event. Let's add a listener for this event.

2. Add GenerateSiteMap Listener

Create a Listeners directory at the root of Jigsaw project directory. Now, create a new file named GenerateSitemap.php inside the Listeners directory. Paste the following code in that file:

<?php
namespace App\Listeners;

use TightenCo\Jigsaw\Jigsaw;
use samdark\sitemap\Sitemap;

class GenerateSitemap
{
    public function handle(Jigsaw $jigsaw)
    {
        $baseUrl = $jigsaw->getConfig('baseUrl');
        $sitemap = new Sitemap($jigsaw->getDestinationPath() . '/sitemap.xml');

        collect($jigsaw->getOutputPaths())->each(function ($path) use ($baseUrl, $sitemap) {
            if (! $this->isAsset($path)) {
                $sitemap->addItem($baseUrl . $path, time(), Sitemap::DAILY);
            }
        });

        $sitemap->write();
    }

    public function isAsset($path)
    {
        return str_starts_with($path, '/assets');
    }
}

We are fetching $jigsaw instance to get all the build related data (base URL, Output path etc). Then using the package we've installed earlier, we are creating a $sitemap object to put all the generated pages from the destination path in the sitemap generator's buffer. Using the isAsset() method, we are avoiding the asset files getting added in the output sitemap.xml file.

This approach has a problem though. If you have pages with non-English title, the build process will trigger exception. Also, if you have space in the page title, same thing will happen.

For example, if you have a page named categories/Web Development , the site builder will throw an exception. Because SPACE is needed to be encoded as %20. Non-English characters are also required to be URL encoded. We can tweak the code a bit to achieve this functionality. Replace the code of GenerateSitemap.php with the following one:

<?php
namespace App\Listeners;

use TightenCo\Jigsaw\Jigsaw;
use samdark\sitemap\Sitemap;

class GenerateSitemap
{
    public function handle(Jigsaw $jigsaw)
    {
        $baseUrl = $jigsaw->getConfig('baseUrl');
        $sitemap = new Sitemap($jigsaw->getDestinationPath() . '/sitemap.xml');

        collect($jigsaw->getOutputPaths())->each(function ($path) use ($baseUrl, $sitemap) {
            if (! $this->isAsset($path)) {
                $path = $this->encodeUrl($path); // encode the file path
                $sitemap->addItem($baseUrl . $path, time(), Sitemap::DAILY);
            }
        });

        $sitemap->write();
    }

    public function isAsset($path)
    {
        return str_starts_with($path, '/assets');
    }

    // encodes SPACE and non-English words
    private function encodeUrl($path)
    {
        $sections = explode("/",$path);
        $last_item = end($sections);
        $encoded = urlencode($last_item);
        $path = str_replace($last_item, $encoded, $path);

        return $path;
    }
}

In the encodeUrl() method, we are replacing only the last word of the path with encoded word.

3. Autoload the Listener directory

To load the Listeners directory, we need to load it in PSR-4 autoload. Let's edit the composer.json file:

{
    "require": {
        "tightenco/jigsaw": "^1.3",
        "samdark/sitemap": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "App\\Listeners\\": "Listeners"
        }
    }
}

4. Add the Listener to the Event

Now, add the GenerateSiteMap Listener to afterBuild event in bootstrap.php file:

$events->afterBuild(App\Listeners\GenerateSitemap::class);

After adding this, the site map generator listener will be triggered after completion of every Jigsaw build. Now run:

./vendor/bin/jigsaw serve

And the Sitemap will be generated at build_local/sitemap.xml path!