Sculpin is a generator for static HTML websites. You write your content in Markdown, Twig Templates or HTML and Sculpin generates the whole page from it. An all-static HTML website is very easy to host, as you only need something to deliver static files like an AWS S3 bucket.

rokka.io is an image CDN that scales and optimizes images for the web. We recently released an update to the Rokka Symfony Bundle that adds support to upload images from Twig templates. As Sculpin is based on Symfony, Bundles can be loaded and configured like with a normal Symfony application.

In this blog post, I will show some examples of how the two can be used to build a static website with images scaled and delivered by rokka.io.

Background

My personal web site has been running with Drupal 6 since forever. I started a couple of times to move the website to a new platform, but always ended up doing something else instead of finishing that project... After 1 year of Covid slowdown, I finally got around to actually finish the rewrite.

In one of my previous efforts, I had written a script that extracted all Drupal nodes into markdown files, including, tags and image references and everything. With Sculpin, I was now able to generate the website. However, the images were always included in the original file size and scaled in CSS by the browser. It works but is of course bad for bandwith. I started pondering to do some automation with Imagemagick to generate different image sizes, but Imagemagick is not trivial to work with.

rokka.io offers a very convenient approach: You upload the image through an API and receive a hash for the image that is based on the image content, so it stays the same if you upload the same image again, but changes if the image is edited in any way. With the hash, you then build an URL to get the image from the rokka.io servers. Besides the hash, the URL specifies the "stack", the name of a configuration how to render the image. The stack allows to scale images and do all kinds of operations. The image is delivered through the rokka.io cloudfront CDN and is highly optimized.

So I wrapped up the Twig integration of the rokka-client-bundle. With release 1.3.0, it is now possible to upload image files to rokka.io while rendering a template.

Note: When using a static site generator, this is the most efficient way of handling things. If you use an interactive CMS, it is better to upload the images when they are uploaded by a content creator. In a CMS, the template with the image might only be rendered when the page is viewed by a client, and the first hit would get a penalty from uploading the image on the fly. If that delay for the first page load is acceptable to you, there is no technical reason preventing you from using the Twig methods in a CMS as well.

Setup

If you don't have an existing sculpin project, set it up with:

composer create-project sculpin/blog-skeleton myblog

Next, add the rokka bundle:

composer require rokka/client-bundle

This installs the code, now we need to enable it. You need to load the bundle in the kernel. If you don't have a kernel in your Sculpin application yet, create it (and otherwise, add the RokkaClientBundle in the getAdditionalSculpinBundles method):

# app/SculpinKernel.php
<?php

use Rokka\RokkaClientBundle\RokkaClientBundle;
use Sculpin\Bundle\SculpinBundle\HttpKernel\AbstractKernel;

class SculpinKernel extends AbstractKernel
{
    protected function getAdditionalSculpinBundles(): array
    {
        return [
            RokkaClientBundle::class,
        ];
    }
}

In the ./vendor/bin/sculpin cli, we now have the rokka commands. If you don't already have an account, you can create one with rokka:user:create. Copy the API key that is returned. If you don't yet have a rokka organization that is used for your project, create one with rokka:organization:create.

Then edit the kernel configuration file to add your API key and the organization name:

# app/config/sculpin_kernel.yml
rokka_client:
    api_key: <generated>
    organization: myorganization

By default, image paths are expected to be relative to the public/ folder. This works for Symfony applications, but the Sculpin skeleton does not generate this folder. You could place the images in the source/ folder, but then the images would also be copied to the output, which wastes space on the server - the images are never loaded from your web server, but directly from rokka. I created a folder rokka_images/ on top level of my Sculpin installation and configured the path to that folder:

# app/config/sculpin_kernel.yml
rokka_client:
    ...
    web_path_resolver:
        root_dir: "%kernel.project_dir%/rokka_images/"

With the help of the sculpin cli, I used the rokka:stack:create command to create 3 stacks:

  • preview: scale to max 130x100 pixel
  • large: scale to max 1024x768
  • original: no operations, to download the image in original size

Rendering Images

Now that everything is set up, we can include the actual images in the page. A single image link with a preview image can be done with:

<a href="{{ 'my_image.jpg'|rokka_stack_url('large') }}">
    <img src="{{ 'my_image.jpg'|rokka_stack_url('preview') }}">
</a>

When the page is generated, the rokka client uploads the image and generates the correct image URL. To avoid having to re-upload every single time, the client stores the rokka hash. By default, the hash is stored in a file named like the image with .rokka.txt appended. The rokka twig integration allows other strategies to store the hashes, but those are not exposed in the bundle configuration. If you want to use a different strategy, you need to define your own services or do a pull request to make the rokka bundle configuration more flexible.

Note: It seems that the twig extension does not realize when an image file is changed but keeps using the existing hash. The easiest i found was to delete the hash file when changing the image, which triggers a re-upload.

Referencing the image directly in a content file is but one option. To specify a teaser image for every blog post, we can have the content file specify an image:

---
title: My Post
image: my-teaser-image.jpg
---
Markdown (resp. HTML) content of the post

In the overview template, we can show the image and link it to the post:

{% for post in page.pagination.items %}
    <article class="list">
        <header>
            <a href="{{ site.url }}{{ post.url }}">
                <h2>{{ post.title }}</h2>
                <img src="{{ (post.image) | rokka_stack_url('teaser') }}"/>
            </a>
        </header>
        ...

And then in the post, we can include the image again, e.g. as banner in the header or whatever makes sense for our design.

The Twig extension offers a couple more filters and functions that can be useful for some scenarios. Have a look at the documentation.

Outlook

From this, we can extend to do image overlays or even a full blown image gallery with a slide show. I used the boostrap modal and carousel components. Find the sample code in this gist.

If you want to see how much rokka.io would optimize the images in your website, use the savings calculator.