Skip to content

Embed a Svelte frontend in a Go binary

This applies to a Go project that embeds a Svelte UI via //go:embed — the pattern keyrx and haileys-app use. Read Explanation: the Svelte frontend track for the full reasoning; this page is the decision + the wiring.

Step 1: decide how the release binary gets the real bundle

Do you already have (or can you add) a //go:generate build-web.sh directive, invoked by goreleaser's before: hooks: - go generate ./...?

  • Yes → use that hook. Do not also wire svelte-build. The hook builds the frontend as part of the ordinary release build — a plain go build/go install works standalone for anyone with Node available, and adding svelte-build on top would build the bundle twice at tag time for no benefit.
  • No → use svelte-build. It builds the bundle as a CI job and hands it to goreleaser as an artifact.

If you're setting this up from scratch and don't have a strong reason otherwise, the go:generate hook is the better default — it's what the projects this track was built for actually use, and it doesn't require any CI-side artifact wiring.

Step 2a: wiring with go:generate (no svelte-build)

Nothing extra to add for the build itself — your existing .goreleaser.yaml's before.hooks already covers it. Just add the quality/security gates:

include:
  - component: gitlab.com/phpboyscout/cicd/[email protected]
    inputs:
      paths: "pkg/studio/web"
  - component: gitlab.com/phpboyscout/cicd/[email protected]
    inputs:
      paths: "pkg/studio/web"
  - component: gitlab.com/phpboyscout/cicd/[email protected]
    inputs:
      paths: "pkg/studio/web"

Step 2b: wiring with svelte-build

include:
  - component: gitlab.com/phpboyscout/cicd/[email protected]
    inputs:
      paths: "pkg/studio/web"
      stage: build
  - component: gitlab.com/phpboyscout/cicd/[email protected]
    inputs:
      paths: "pkg/studio/web"
  - component: gitlab.com/phpboyscout/cicd/[email protected]
    inputs:
      paths: "pkg/studio/web"
  - component: gitlab.com/phpboyscout/cicd/[email protected]
    inputs:
      paths: "pkg/studio/web"
  - component: gitlab.com/phpboyscout/cicd/[email protected]

goreleaser:
  needs:
    - job: svelte-build
      artifacts: true

The needs: overlay is required — without it, goreleaser runs without waiting for (or downloading) the built bundle artifact.

Step 3: couple the Go tests to frontend changes (required either way)

Your Go embed test(s) — typically an embed_test.go asserting on the served SPA — depend on what's actually in the embedded bundle directory, but go-test's default change-detection filter only watches Go source and module files. Extend it so a frontend-only change still runs those tests:

include:
  - component: gitlab.com/phpboyscout/cicd/[email protected]
    inputs:
      changes:
        - "**/*.go"
        - "**/go.mod"
        - "**/go.sum"
        - ".golangci.*"
        - ".gitlab-ci.yml"
        - "pkg/studio/web/**"   # your frontend root

This step is easy to skip and the failure mode is silent — a PR that only touches the frontend could merge having never exercised the Go code that serves it. See How-to: add change-detection to a gate for the general mechanics.

Step 4: verify

Open an MR that only touches pkg/studio/web/. Confirm: go-test runs (not skipped); svelte-lint/svelte-test/svelte-security run; if using svelte-build, its job runs too. Then cut a release tag and confirm the published binary serves the real UI, not the committed placeholder.

See also