Swift for Static Sites
I recently tried to answer a practical question: if I move a visually complex blog theme to a Swift static site generator, which one should I use?
I went through Saga, Toucan, Publish, Ignite, and Raptor, then compared them with Web-native tools such as Astro, Hexo, Hugo, and Jekyll.
After reading their source and examples and trying them myself, I found that the choice depends less on feature count than on where Swift sits in the stack. A SwiftUI-like component API and a Swift program that generates ordinary HTML may look similar in a small demo, but they behave very differently once the design needs custom CSS.
The browser is the UI runtime
SwiftUI works on iOS because its abstractions connect to a system UI runtime:
iOS:System UI runtime→ SwiftUI Button / VStack / NavigationStack→ Your Swift codeWeb:Browser runtime (DOM + CSS + JS)→ HTML <button> / <div> / <article>→ CSS grid / flex / selectors→ Your HTML/CSS/JSA SwiftUI Button is connected to platform behavior including accessibility, focus, animation, and input. A button declared through Raptor or Ignite eventually becomes:
<button class="...">Save</button>plus CSS. The browser does not know about the Swift component that produced it; it only receives HTML, CSS, and JavaScript.
This is not an argument against declarative UI. React, Vue, and Astro are declarative too. The important difference is the object being declared and how closely it matches the browser’s own model.
| Framework | What you declare | Distance from the platform |
|---|---|---|
| SwiftUI | Native UI tree | Very close |
| React / Vue | DOM / component tree | Very close |
| Astro | HTML + islands | Very close |
| Saga / Publish | HTML output tree | Close |
| Ignite | Swift components (Bootstrap-like) | Medium |
| Raptor | SwiftUI-like UI + style system | Farther |
SwiftUI maps closely to its platform. React still models the DOM, and Astro treats HTML as a first-class part of the component. Ignite and Raptor move the working model farther upward into a Swift component tree. That can be pleasant for a simple page, but the extra translation becomes visible in a heavily customized theme.
Where the component DSL stops helping
Ignite-style components are convenient for straightforward UI:
Text("Hello")Button("Read More")Grid { Card { ... }}With Bootstrap underneath, layout, spacing, responsiveness, and basic visual hierarchy arrive quickly. That is a good fit for documentation, portfolios, basic blogs, and other sites that can stay near the framework’s built-in vocabulary.
A migrated visual theme is usually less cooperative. It may depend on CSS such as:
.card::before.sidebar:has(.active)grid-template-columns: minmax(0, 1fr) 18remposition: stickybackdrop-filtermask-imagecontainer queriesOnce the built-in components no longer express the design, the Swift code falls back to lower-level HTML wrappers:
Tag("aside") { ... }.class("layout-shell__sidebar")The CSS still has to be written. The page is now split between a SwiftUI-style vocabulary and direct HTML and CSS, so the abstraction no longer removes much work.
The useful range for this approach is still real:
Simple sitesDocsPortfoliosBasic blogsBootstrap-like layoutsThe difficulty begins when a theme originally built in Astro or Hexo relies on precise selectors, pseudo-elements, layout rules, and browser-specific behavior. This is a boundary of the abstraction, not a defect in declarative UI or in the frameworks themselves.
Saga keeps the boundary visible
Saga takes a different route. It uses Swift for the parts where Swift is useful and leaves browser concerns in Web-native forms:
Swift:Content modelPipelineGeneration logicType safety
Web:HTML structureCSS stylingJavaScript behaviorA template can look like this:
article(class: "mx-auto max-w-3xl px-6 py-12") { h1(class: "text-4xl font-bold tracking-tight") { item.title } div(class: "prose prose-slate dark:prose-invert") { raw(item.body) }}This is Swift generating HTML, not an attempt to reproduce SwiftUI in a browser. The division remains easy to inspect:
Swift-native:Types, functions, composition
Web-native:HTML, CSS, browser semanticsFor this kind of project, that directness matters more than having a larger UI abstraction.
Tailwind makes Saga more practical
Without Tailwind, a Saga template resembles conventional HTML with named CSS classes:
article(class: "post-card") { h2(class: "post-card__title") { item.title }}With Tailwind, the layout and styling remain next to the generated HTML:
article(class: "group rounded-3xl border border-slate-200 bg-white p-6 shadow-sm transition hover:-translate-y-1 hover:shadow-lg") { h2(class: "text-2xl font-semibold tracking-tight") { a(href: post.url) { post.title } }}There is no need to translate each CSS concept into a Swift modifier API. Tailwind is still CSS, expressed through utility classes, so the browser-facing model stays recognizable.
Raptor and Ignite fit different jobs
Raptor is more ambitious than a conventional static site generator. It defines a broad site model:
SitePagePostPageCategoryPageLayoutThemeStylePostWidgetIt also integrates with Vapor for server-side rendering. That makes it interesting for Swift-first content platforms, static and dynamic hybrid sites, and projects that need backend integration.
The site model does not automatically make a complex front end easier to express, however. When the theme exceeds its UI and style system, the implementation returns to:
Tag + Div + Class + CSSAt that point I would want the higher-level model to solve a separate problem—content architecture or server integration, for example—because it is no longer reducing the front-end work.
Ignite makes a more pragmatic trade:
Swift API + BootstrapIt is fast for a small site, portfolio, or documentation project. The trade-off is that Bootstrap’s structure and visual assumptions become harder to hide when the site needs a distinctive theme.
Why I would still use Astro for the complex theme
If Swift is not itself a project requirement, Astro remains the safer choice for this particular job:
- HTML, CSS, and JavaScript are first-class;
- its components stay close to browser primitives;
- Tailwind integration is straightforward;
- Content Collections provide structure;
- the ecosystem is mature.
That does not make Swift incapable of building websites. It means the cost of the Swift abstraction has to buy something the project needs.
The two Swift approaches I compared can be summarized as:
SwiftUI-style Web DSL (Raptor / Ignite)
Swift expresses UI→ translated into HTML/CSSSwift-native generation + Web-native UI (Saga)
Swift handles logic and structureHTML/CSS/JS express the UIFor a highly customized visual site, I prefer the second model. Swift still provides types, functions, composition, content modeling, and generation logic, while HTML, CSS, and JavaScript retain control of the UI.
My practical shortlist after the comparison is:
Not using Swift: Astro + TailwindUsing Swift seriously: Saga + TailwindQuick Swift site: IgniteExploring Swift Web frameworks: Raptor