Post

Three Releases Without a Post: Structured Data, Languages and a Security Fix

The last post here covered the API documentation in 1.8.69. Three releases have gone out since without one. Most of what changed came from building real sites on ssg, a multilingual marketing site and a recipe blog, where we had to script around something ssg did not do. This post goes through those gaps and what ssg does about them now.

Recipes without the backfill script

A recipe site migrated from WordPress had 26 posts and a Python script. The script read each post, found the ingredients list and the method, and copied both into the post's schema: front matter so the page could carry a Recipe rich result. It had to run again every time a recipe was added.

In 1.8.72 ssg reads them itself. When a page's structured data is a Recipe and its schema: has no recipeIngredient or recipeInstructions, ssg takes them from the post:

  • the list under a heading called "Ingredients", "Składniki", "Zutaten", "Ingrédients", "Ingredientes" or "Ingredienti";
  • the steps under "Method", "Instructions", "Preparation", "Przygotowanie", "Zubereitung" and the like.

A heading can be ## Ingredients or a bold line. It can go on ("Ingredients for 16 servings"). The steps can be a numbered list, plain paragraphs, or paragraphs under <strong>Step 2</strong>. recipe_headings adds a site's own names. recipeCategory now comes from the post's categories. Anything you do write in schema: still wins, field by field.

{{ duration "PT1H25M" }} prints "1 hr 25 min", in six languages. Structured data keeps times as ISO 8601, so a theme no longer needs a second, hand-written copy to show them. check_schema_recommended: true lists what a recipe card would show if it were there, such as yield, time and nutrition, as advice that never fails a build.

More structured data, no extra front matter

  • FAQ. A faq: list in front matter becomes an FAQPage block.
  • Listings. With seo: true, every listing and archive page is an ItemList of the posts it shows, in order.
  • Authors. An author's archive is that author's ProfilePage.
  • The site. The front page names the Organization behind it, using the name, logo and social profiles already set in marketing:.

A theme that writes its own JSON-LD keeps full control and gets these blocks as .StructuredData to include if it wants.

A CV page from Schema Resume

Schema Resume is a JSON format for a CV. Point ssg at one:

1resume:
2  file: cv.json

ssg builds a page at /cv/ that renders in any theme, with ProfilePage structured data that lists your roles, education, credentials, skills and languages in schema.org terms. It also publishes the source document next to the page as resume.json. Your e-mail, phone and street address stay out of the page, the structured data and that file unless you set contact: true.

A front page per language

On a site in Hindi, English and Polish, each language needed its own home page. A page with link: "/en/" worked, but ssg did not know it was the English front page. It wrote the English post listing to the same file, and whichever was rendered last won. Since 1.8.70, a page at a language's root is that language's front page, and its listing moves to /en/blog/. The listing pages also gained what a theme needs for its <head>: the language, the canonical address of each paginated page, and hreflang for every language.

Workers

  • Newsletter. ssg new worker newsletter is a sign-up form backed by Cloudflare D1. It stores the consent text with each address, checks Turnstile, and supports one-click unsubscribe and double opt-in. The admin view exports CSV.
  • Turnstile secrets. Workers in one Cloudflare Pages project share one environment. Setting TURNSTILE_SECRET for the comments Worker used to turn on a check in the cookie-consent log that the banner never passed, so every consent record was dropped without an error. Each Worker now reads its own secret first.
  • Cookie dialog focus. The consent dialog now keeps keyboard focus inside it and hands it back to the element that opened it when it closes.

Smaller fixes worth knowing

  • Render hooks (1.8.70) are now looked up in the theme first, so a theme can ship them.
  • The snap's private /tmp. The snap build now warns when you point it at /tmp, which is the snap's own private copy. Before 1.8.70 it reported success, and the files were nowhere you could find them.
  • Docker preview. In the Docker image, which has no Node, the preview now says once that Functions are not served, instead of failing every time it starts. watch_runner: none silences it.

1.8.71: update now

1.8.71 is a security release. Dependabot did not report this: it watches dependencies, and these vulnerabilities were in Go itself. govulncheck found 13 advisories in Go 1.27.1's standard library and in golang.org/x/net that ssg's own code calls, in HTTP/2, net/http, crypto/tls, html/template and os. That covers the preview server, every remote fetch and every page ssg renders. ssg 1.8.71 is built with Go 1.27.2. Nothing in a site changes; replace the binary, package or image. Building ssg from source now needs Go 1.27.2.

The changelog has everything, and UPGRADING has the one thing in 1.8.72 to check: recipe pages may now publish ingredients, steps and a category they did not publish before.