News · · By Cole Speelman

Sandwich Hime v1.0.0: write HTML, ship Go.

Sandwich Hime v1.0.0 is here. If you're joining us at launch, welcome.

Sandwich Hime is an HTML-first template engine for Go. You write .sando templates; Hime-san compiles them into typed Go components. Your application renders them with its own data. You keep your router, database, CSS, JavaScript, and deployment choices.

The part I'm most excited to share isn't a feature list. It's how much easier this has made my own websites to build, understand, and maintain.

Built through real work

I've been using Sandwich Hime in production and private projects throughout its development. The biggest improvement has been reduced cognitive load: less mental translation between what a page should look like and the source that builds it. I can open a template, see its structure, follow its typed inputs, and make a change without reconstructing the page in my head.

That matters when returning to a repository, reviewing an edit, or sharing work with someone else. The HTML stays recognisable. The Go stays Go. Small components give related markup a home, and the generated source remains available to inspect and commit alongside the templates.

You can see that work in public at Gamertan.com and here at SandwichHime.com. These are websites I use to present and maintain the work itself: project pages, documentation, tutorials, and news. Alongside private development, they have given me practical places to refine the templates and the workflow.

Our production experience includes a migration spanning 47 page families and 106 typed components. That gave us a larger proving ground for readable source, typed composition, and rendered-page parity.

Clearer for people. Better for AI-assisted work.

The improvement to my AI-assisted workflow has been substantial, too. Readable markup and explicit component inputs make it easier to direct an edit, see what changed, and review the result. Generated Go brings those changes back into the application's normal compilation and tests.

Google's Cameron Balahan and Richard Seroter recently made a related case in “Why Go is an Ideal Language for AI-Assisted Software Engineering” (August 11, 2026): as AI accelerates code generation, readability and consistent tools for reviewing, verifying, and maintaining that code become more important. That closely matches my experience building with Hime.

These are observations from my own work, not a measured productivity study. But they explain the direction: source that is easier for a person to understand also gives an AI assistant clearer material to work with—and gives the person reviewing its work less to untangle.

A page should still look like a page

Let's build a tiny sandwich field guide. The taxonomy is playful; the typed Go and working page are real. Start a module and install the runtime, then the compiler:

mkdir sandwich-field-guide
cd sandwich-field-guide
go mod init example.com/sandwich-field-guide
go get gamertan.com/sandwich-hime/sando@v1.0.0
go install gamertan.com/sandwich-hime/cmd/himesan@v1.0.0
mkdir views

Put Go's install directory on your PATH so the himesan command is available. First, give the page an ordinary Go data model in views/sandwich.go:

package views

type Sandwich struct {
    Name     string
    Bread    string
    Slices   int
    Fillings []string
    Sauce    string
    Steps    []string
}

func (s Sandwich) Classification() string {
    switch {
    case s.Slices == 1:
        return "Open-faced"
    case s.Slices == 2:
        return "Closed"
    case s.Slices > 2:
        return "Club"
    default:
        return "Unclassified specimen"
    }
}

Our field guide classifies by bread-slice count. Feel free to dispute its scientific credentials. Sauce, however, is non-negotiable. Each specimen includes a recipe for one sandwich. Create a reusable card in views/sandwich_card.sando:

<?sando go
package views

func SandwichCard(sandwich Sandwich)
?>
<article>
  <h2><?= sandwich.Name ?></h2>
  <dl>
    <dt>Classification</dt>
    <dd><?= sandwich.Classification() ?></dd>
    <dt>Bread</dt>
    <dd><?= sandwich.Slices ?> × <?= sandwich.Bread ?></dd>
  </dl>
  <h3>Fillings</h3>
  <ul>
    <? for _, filling := range sandwich.Fillings { ?>
      <li><?= filling ?></li>
    <? } ?>
  </ul>
  <h3>Sauce</h3>
  <p><?= sandwich.Sauce ?></p>
  <h3>Make it</h3>
  <ol>
    <? for _, step := range sandwich.Steps { ?>
      <li><?= step ?></li>
    <? } ?>
  </ol>
</article>

Then compose the page body in views/field_guide.sando. Conditions and loops are ordinary Go; <?~ ... ?> renders another component:

<?sando go
package views

func FieldGuide(sandwiches []Sandwich)
?>
<h1>A field guide to sandwiches.</h1>
<? if len(sandwiches) == 0 { ?>
  <p>No specimens remain. Someone has eaten the evidence.</p>
<? } else { ?>
  <? for _, sandwich := range sandwiches { ?>
    <?~ SandwichCard(sandwich) ?>
  <? } ?>
<? } ?>

The outer document belongs in views/layout.sando. Its body accepts the same sando.Component contract as any other generated template:

<?sando go
package views

import "gamertan.com/sandwich-hime/sando"

func Layout(title string, body sando.Component)
?>
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title><?= title ?></title>
</head>
<body>
  <a href="#main">Skip to content</a>
  <main id="main"><?~ body ?></main>
</body>
</html>

Finally, create main.go. A handler calls the generated constructors just like other Go functions, nesting the field guide inside the layout:

package main

import (
    "bytes"
    "log"
    "net/http"

    "example.com/sandwich-field-guide/views"
    "gamertan.com/sandwich-hime/sando"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /{$}", home)
    log.Fatal(http.ListenAndServe("127.0.0.1:8080", mux))
}

func home(w http.ResponseWriter, r *http.Request) {
    sandwiches := []views.Sandwich{
        {
            Name: "The Hime Club", Bread: "Sourdough", Slices: 3,
            Fillings: []string{
                "60 g mature cheddar", "1 small tomato, thinly sliced",
                "2 crisp lettuce leaves", "4 pickle slices",
            },
            Sauce: "Mix 2 tbsp mayonnaise, 1 tsp Dijon mustard, and 1/2 tsp lemon juice.",
            Steps: []string{
                "Lightly toast the three slices of sourdough.",
                "Pat the tomato dry; spread sauce on the inward-facing bread surfaces.",
                "Layer the cheddar, tomato, lettuce, and pickles across both tiers.",
                "Close the sandwich, halve it, and serve immediately.",
            },
        },
        {
            Name: "Open Source Toast", Bread: "Rye", Slices: 1,
            Fillings: []string{
                "100 g mushrooms, sliced", "1 tsp olive oil",
                "1 small pinch dried thyme", "Salt and black pepper, to taste",
            },
            Sauce: "Mix 1 tbsp plain Greek yogurt, 1 tsp tahini, 1/2 tsp lemon juice, and a pinch of salt.",
            Steps: []string{
                "Brown the mushrooms in olive oil with thyme and a pinch of salt.",
                "Toast the rye and spread it with the lemon-tahini yogurt.",
                "Pile on the warm mushrooms, finish with black pepper, and serve.",
            },
        },
    }
    page := views.Layout("Sandwich field guide", views.FieldGuide(sandwiches))

    var output bytes.Buffer
    if err := sando.Render(r.Context(), &output, page); err != nil {
        log.Printf("render home: %v", err)
        http.Error(w, "Could not render page", http.StatusInternalServerError)
        return
    }
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    _, _ = w.Write(output.Bytes())
}

Generate the neighbouring .sando.go files, check them, and run the app:

himesan generate ./views
himesan check ./views
go run .

Open http://127.0.0.1:8080/. Set the sandwiches slice to nil to try the empty state. Later, that slice can come from your database or another service without changing the templates' contract.

You now have two sandwiches, a deliberately debatable taxonomy, and actual recipes—including the sauce. Add another specimen by adding data, not another template.

Go checks the component arguments. Hime escapes the names, classifications, and fillings for their HTML contexts. Buffering lets the handler return a clean error before sending a partial page. Styling and production server configuration remain application choices; this example deliberately keeps both minimal.

This is ahead-of-time template compilation, not a requirement to prebuild every page. Your pages can still use live database results, account details, or whatever else your application needs.

Fast pages, ordinary Go deployments

Building fast, compiled Go websites this way has been a joy. Generate, test, build the application binary, and deploy it with its required assets and configuration. Production uses the generated code and the small sando runtime; the template compiler and development tools stay in development.

For visitors, the benefit should be straightforward: responsive pages that work reliably. Server-rendered HTML doesn't require a client-side framework just to display the page; JavaScript remains available where it helps. Compiled rendering, contextual escaping, and Go's tooling give us useful foundations for performance, security, and stability. We still have to build and test the application well.

Version 1.0 establishes our stable baseline, not the end of the work.

Build something small with it

The step-by-step tutorial goes further into the rendering and testing boundaries. You can also clone the tutorial starter or use the native Mac/Linux downloads.

It's free to use, including for commercial applications: AGPL-3.0-only compiler, Apache-2.0 runtime. The licensing guide explains the boundaries and generated-output permission.

Bring your own second opinion

If AI is part of your workflow, give your assistant the website or public repository and ask for a candid assessment in the context of your own project. Here's a starting prompt:

Assess Sandwich Hime against my current approach to Go web development. Examine its design philosophy, source, documentation, and tradeoffs. Where would it help my workflow, and where might it add friction? Compare relevant alternatives, cite the sources behind your conclusions, and distinguish documented claims from things we should test. Then help me build one small page so we can evaluate it ourselves.

Follow the references, inspect the generated Go, and run the result. An AI-assisted review is a useful starting point; trying it in your own work is how you find out whether it fits.

Part of why I care about this project is simple: I want more people—including kids learning to code—to discover that they can understand and build these things themselves. Go, HTML, a useful little tool, and somewhere to begin.

Try it on a small page. Tell me what felt good, what confused you, and what you made. That feedback is where the next chapter starts.

After all, the proof is in the sandwich. 🥪👸