Drupal Tome

I've moved from publishing the site directly through Drupal to a Jamstack setup, where the site is published as static files (converted to HTML). The module I'm covering this time, 『Tome』, converts a Drupal-built website into HTML while preserving its full structure. It's the key piece behind both 『building』 and 『publishing』 the site under the current Jamstack setup.

Here I've put together the process of installing 『Tome』 and converting a Drupal-built website into HTML, structure included.

『Table of Contents』

  1. Top --- Overview of Tome
  2. #01 Installing Tome --- Installing and enabling Tome, and generating actual 『static files』
  3. #02 Adjustment --- Checking the generated files and fixing issues
  4. #03 Conclude --- Thoughts after putting Tome into practice

Clicking the numbers in the simple navigation bar at the bottom of the page will jump to that part of the article. Feel free to use it when re-reading.

In the previous article I ran quickly through the steps involved in going Jamstack, so let's recap.

Work on the MacBook Pro

1.  Set up an environment using OrbStack + DDEV + Drupal + Tome that lets me handle the entire site build on my MacBook Pro.

2.  Using Tome — a Drupal module — to convert the site's page structure into HTML.

Deploying to AWS

3.  The HTML-converted pages are uploaded to Lightsail via sFTP.

4.  During the upload to Lightsail, only the differences are updated.

5.  Once the upload to Lightsail finishes, CloudFront reflects the changes immediately and the site is live.

This article covers the process behind step ” 2.  Using Tome — a Drupal module — to convert the site's page structure into HTML. ”

Tome.

『Tome』 is a module that converts a Drupal-built website into 『static files: HTML』.

Quoted from Tome on drupal.org

  • Tome is a static site generator and a static storage system for content.
  • Tome is split into two sub-modules that can be used independently.
  • If you just want to generate a static site, enable Tome Static.
  • If you just want to store content and configuration in Git, enable Tome Sync.
  • If you want both, enable Tome.

Tome is made up of 『Tome Static』, which generates static files, and 『Tome Sync』, which stores Drupal's content and configuration on GitHub. Static content built with 『Tome Static』 can be deployed via Git using 『Tome Sync』.

Since I don't use Git for deployment, I only install and use 『Tome Static』.

Installing Tome

Now I'll install Tome. As with any other module, I use Composer. The current latest version of Tome is 『8.x-1.16』. It runs without any issues on Drupal 11.4.4, which I have installed on my MacBook.

* During the Drupal 11.4.0 major update, a Symfony-related syntax error occurred in Tome Sync, but it was resolved in 11.4.2. Since I don't use Git for deployment, my workaround for the error that came up in 11.4.0 was to uninstall Tome Sync.

$ composer require 'drupal/tome:^1.16' -W
$ drush updatedb
$ drush cr

That completes the Tome installation.

Using Tome

Now that Tome is installed, I'll enable it.

In the admin's 『Extend』

Tome

  1. Tome — everything you need for a static site.
  2. Tome Base — contains services and traits shared between Tome Sync and Tome Static.
  3. Tome Static — exports the entire Drupal site as static HTML.
  4. Tome Static Cron / Processes static paths in a cron queue worker.
  5. Tome Static Super Cache — changes core's and Tome Static's caching behavior so static builds can be cached for longer.
  6. Tome Sync — installs Drupal content from flat files and keeps content in sync.
  7. Tome Sync Autoclean (Experimental)  / Automatically deletes files that are unused. This module could definitely lead to data loss, use with caution!

Checking items 1 through 6 and installing them gives you access to all of Tome's functionality. I haven't enabled 6. Tome Sync or 7. Tome Sync Autoclean.

With that, Tome — for generating static files — is ready to use.

Tome Static

Now let's actually use Tome to turn the Drupal-built site into static files.

In the admin's 『Configuration』

  1. Tome Static  Generate a static site / Create a static copy of your site using Tome.  
  2. Download the static site / Download the latest static build.
  3. Preview the static site / Preview the latest static build.

The above is what's displayed. Generating the static files is done from ”1. Tome Static  Generate a static site”.

Home  >  Administration  >  Configuration  >  Tome  >  Tome Static  >  Generate static site
Generate static site
[Build]
Submitting this form will initiate a build of all uncached static pages site using Tome. Existing files in the static export directory (../html) will be overridden.
[Base URL]
he absolute URL used for generating static pages. This should match the domain on the site where the static site will be deployed.
『Here I enter the actual URL where this will be published』 for my site, that's 『https://inter-est01.com』
[Submit]

On the 『Generate static site』 page, entering the actual published URL into 『Base URL』 and submitting generates the static files. Skip this and the local domain referenced throughout the site won't be converted to the published domain — for example, the meta tag URLs would still show the local domain.

$ ls
composer.json
composer.lock
content
files
html  //the generated html directory
recipes
vendor
web
$ cd html
$ ls
contact     ja         search             tarm-tag-outerwear    term-article-web
core        lifestyle  sitemap.xml        tarm-tag-tops         term-lifestyle-fashion...
...
// Directories are created inside the html directory.
$ cd creation
$ ls
index.html  learn002  learn005  learn008  learn011  ...
$ cd learn002
$ ls
index.html
// HTML is generated as creation/learn002/index.html.

The generated files appear in a directory called 『html』 at the root of the DDEV project, where the entire Drupal-built site is converted to HTML (as directory/index.html) while preserving its full structure.

Adjustment

Using Tome, HTML files are built while preserving the site's structure.

Before publishing, I check things over.

Preview

Tome has a 『Preview』 feature, but the pages it displays don't load CSS or JS. To get an accurate picture of what the published pages will actually look like, I spin up a simple web server to check.

$ cd html
$ python3 -m http.server 8000 --bind 127.0.0.1
Serving HTTP on 127.0.0.1 port 8000 (http://127.0.0.1:8000/)
...
// Opening http://127.0.0.1:8000/ in a browser shows the static site.
// Stop with Ctrl+C

Checking it with the simple web server, the Drupal-built site displays correctly as static files. The static conversion completed without issues.

Metatag

I set the meta tags for each page using Metatag, a Drupal module. It's an excellent module — install it, leave the defaults as they are, and the basic meta tags get inserted into pages automatically.

Since Drupal is a CMS, its URLs end 『without a trailing slash』.

<link rel="canonical" href="https://inter-est01.com/ja" />
<link rel="alternate" hreflang="x-default" href="https://inter-est01.com/en" />
<link rel="alternate" hreflang="ja" href="https://inter-est01.com/ja" />
<link rel="alternate" hreflang="en" href="https://inter-est01.com/en" />

This is where a problem comes up. Since the actual published site is HTML, the published URLs end 『with a trailing slash』.

The issue is this: search engine crawlers like Google's recognize the URL 『without a trailing slash』 as the canonical page, while the actual canonical page is the one 『with a trailing slash』. So the URL the crawler thinks is canonical gets treated as a 301, and every crawl throws off a large number of 301s across the entire site.

This causes no problems for publishing or for users browsing the site, but from a search-indexing standpoint, it means the canonical page itself is being redirected.

Since the actual URL ends 『with a trailing slash』, I changed the canonical page in the meta tags from ending 『without a trailing slash』 to ending 『with a trailing slash』.

Since "canonical" can use "Token[node:url]", entering "[node:url]/ : add a trailing slash" into Metatag's token field applies it across every page. The problem is that I couldn't find a token for URLs that include the language prefix, so for "hreflang" I have to write the trailing slash into each page's URL by hand.

<link rel="canonical" href="https://inter-est01.com/ja/" />
<link rel="alternate" hreflang="x-default" href="https://localname.ddev.site/en/" />
<link rel="alternate" hreflang="ja" href="https://localname.ddev.site/ja/" />
<link rel="alternate" hreflang="en" href="https://localname.ddev.site/en/" />

Tome converts token-based URLs for me, but it doesn't touch local domains I've written in by hand. After generating the static files, I run the following script to convert them all in one pass. I run it from one directory above the DDEV project directory.

find localname/html/ -name "*.html" -exec sed -i '' 's|https://localname\.ddev\.site/|https://inter-est01\.com/|g' {} +

Running that converts the meta tags to the following.

<link rel="canonical" href="https://inter-est01.com/ja/" />
<link rel="alternate" hreflang="x-default" href="https://inter-est01.com/en/" />
<link rel="alternate" hreflang="ja" href="https://inter-est01.com/ja/" />
<link rel="alternate" hreflang="en" href="https://inter-est01.com/en/" />

 

The published domain "href="https://inter-est01.com/en/" now has a trailing 『/』 added.

sitemap.xml

Tome doesn't convert the sitemap's local domain to the published domain.

 sitemap.xml
 <url>
  <loc>https://localname.ddev.site/ja</loc>
  <xhtml:link rel="alternate" hreflang="ja" href="https://localname.ddev.site/ja"/>
  <xhtml:link rel="alternate" hreflang="en" href="https://localname.ddev.site/en"/>
  <lastmod>2026-07-18T04:21:07+09:00</lastmod>
  <changefreq>daily</changefreq>
  <priority>1.0</priority>
 </url>

Since this is a local file, I use a text editor to replace it — swapping the domain name and adding a trailing 『/』 to the URL.

"href="https://localname.ddev.site/ja"

I replace this with

"href="https://inter-est01.com/ja/"

using a text editor. I use CotEditor or BBEdit.

 <url>
  <loc>https://inter-est01.com/ja/</loc>
  <xhtml:link rel="alternate" hreflang="ja" href="https://inter-est01.com/ja/"/>
  <xhtml:link rel="alternate" hreflang="en" href="https://inter-est01.com/en/"/>
  <lastmod>2026-07-18T04:21:07+09:00</lastmod>
  <changefreq>daily</changefreq>
  <priority>1.0</priority>
 </url>

 

This converts it to the published domain, with a trailing 『/』 added to the domain.

robot.txt

Tome doesn't generate robots.txt. I handle this by copying Drupal's default "web/robots.txt" into the 『html』 publish directory and uploading it along with everything else.

All set

Publishing prep is now complete.

『Generating static files with Tome』  ---  『Tome Static』

『Checking the generated HTML』  ---  『Verified with a simple web server』

『Fixing the meta tag inconsistency』  ---  『Handled with Metatag and a script』

『Converting the sitemap.xml URLs』  ---  『Handled with find-and-replace in BBEdit』

I'd assumed that generating the files with Tome Static and replacing the local domain in sitemap.xml would be enough, but looking at the crawl results in Search Console, I found a large number of 301s — which meant fixing the meta tag inconsistency, something I hadn't planned for at all.

Thinking about it, it makes complete sense — a site built around the CMS default of URLs 『without a trailing slash』 gets converted into static HTML and published, so of course the canonical page ends up 『with a trailing slash』. But it wasn't until I actually published it and saw Google's crawler generating 301s that I noticed and fixed it.

Strictly speaking, the internal links throughout the site are also built with URLs 『without a trailing slash』. I considered fixing those too, but there's no end to it, so I've left them as they are.

This was my first attempt at Jamstack with Drupal, and seeing problems that only surface once you actually build and publish the site was itself a valuable takeaway.

Conclude

Adding Tome to my local environment — OrbStack, DDEV, and Drupal on my MacBook Pro — I exported the Drupal-built site as static files and checked the output. I'd already confirmed before publishing that sitemap.xml isn't something Tome generates and that robots.txt doesn't get generated either, so I'd already dealt with both.

The 『presence or absence of a trailing slash in the URL』 — a difference that comes from how a CMS and published HTML structure their URLs — never crossed my mind. Viewed in a browser, everything redirected cleanly, so I assumed it would cause no problems at all for ordinary users browsing the site.

It was only after checking Search Console that the issue surfaced — Google's crawler wasn't recognizing the canonical page the way I expected — and dealing with that ended up taking a fair amount of time.

The main reason it took so long was that setting up 『hreflang』 required me to check which tokens and entities Metatag could use. I hadn't paid much attention to tokens before, so I went looking for the right ones and tried chaining various tokens and entities together — something I hadn't done much of before. In the end I couldn't find a way to automate this across every page through tokens, so I decided writing each URL in by hand was the most reliable and, in the end, the quickest path — and that's what I did.

Meta tags aside, using Tome makes it remarkably easy to convert a Drupal-built site into HTML, structure and all, just as before. With this process in place, the Jamstack plan becomes a reality.

Next article

CloudFront and Lightsail (working title)

Since this Jamstack setup serves static files through a CDN, I'll cover CloudFront — the core of that setup — and Lightsail, where the static files live.

Last update
Contributor
S.Takeda
Article

Powered by Drupal 11.4.4 and Bootstrap5 4.0.8

Published AWS Jamstack by Drupal