Victor is our idea of a command-line tool which merges static site generation traditions with modern reactive web development. Drawing inspiration from Hugo’s templating philosophy, Fornax’s F# script architecture (itself Jekyll-inspired), and FlightDeck’s experiments in server-side integration, Victor creates a unique “full stack” development experience where static content and dynamic components coexist seamlessly with the “server side” through Partas.Solid’s cohesive binding syntax and CloudflareFS tooling.
Unlike traditional SSGs that treat JavaScript components as foreign entities to be embedded, Victor generates static sites where Partas.Solid integrated elements such as the Kobalte component system are first-class citizens. This creates Multi-Page Applications (MPAs) with selective reactivity - combining the performance of static HTML with the interactivity of SolidJS exactly where needed. “Server side” is in quotes as a term of convenience. The SPEC stack utilizes Cloudflare whose central concept is the “Worker.” It’s a serverless concept that utilizes V8 isolates to execute JavaScript with distributed compute “close to the customer” on their vaunted edge network. The SPEC stack, and by extension Victor, has designs to fully leverage that fast and efficient cloud infrastructure for a variety of solution patterns.
Design Intent and Community Vision
This document outlines the architectural design for Victor, a forward-looking CLI tool that aims to unify static site generation with modern component frameworks. The ambitious scope presented here establishes our intent for community collaboration and open-source development. We invite feedback, contributions, and critical analysis as we work toward realizing this vision.
The architecture described represents our design goals rather than current implementation. By articulating these patterns clearly, we hope to attract contributors who share our vision of bridging the gap between static generation and dynamic interactivity while maintaining F#’s type safety and functional principles.
The Partas.Solid Ecosystem: Architectural Foundation
To understand Victor’s design, we must first appreciate what Partas.Solid brings to the F# ecosystem. This isn’t merely a SolidJS port - it’s a comprehensive binding architecture that unifies access to the modern JavaScript component ecosystem while maintaining F#’s type safety and functional principles.
The Uniform Binding Architecture
A major Partas.Solid innovation lies in its approach to bindings. The design ensures that every component - whether a native HTML element, a Kobalte headless UI component, or a third-party library - follows identical syntax patterns. Its uniformity isn’t cosmetic - it’s architectural. The AST transformation ensures that F# developers never need to context-switch between different binding styles.
Design System Integration: DaisyUI and Tailwind CSS
A critical aspect of Victor’s design involves deep integration with Tailwind CSS and DaisyUI for consistent, themeable styling. The vision includes:
// Proposed DaisyUI component bindings
[<SolidComponent>]
let NavigationCard (title: string) (href: string) =
div(class'="card w-96 bg-base-100 shadow-xl") {
div(class'="card-body") {
h2(class'="card-title") { title }
p() { "Click to navigate to this section" }
div(class'="card-actions justify-end") {
a(href=href, class'="btn btn-primary") { "Go" }
}
}
}
// Theme-aware components using Tailwind's design tokens
[<SolidComponent>]
let ThemedLayout (theme: Theme) =
div(``data-theme``=theme.name, class'="min-h-screen bg-base-100 text-base-content") {
nav(class'="navbar bg-base-200") {
div(class'="navbar-start") { /* Logo */ }
div(class'="navbar-center") { /* Navigation */ }
div(class'="navbar-end") { /* Theme switcher */ }
}
main(class'="container mx-auto px-4 py-8") {
props.children
}
}
The design envisions seamless integration where DaisyUI’s semantic class names and Tailwind’s utility classes work together, providing both rapid development and consistent design language.
The Standing Ecosystem
Partas.Solid currently provides bindings for essential libraries, with plans to expand based on community needs. The design philosophy prioritizes accessibility, performance, and developer experience:
Kobalte provides a complete headless component system - dialogs, tooltips, comboboxes, and more - with full accessibility built in. The vision for Victor includes deep integration with these components:
[<SolidComponent>]
let ContactForm() =
Dialog.Root() {
Dialog.Trigger(as'="button", class'="btn btn-primary") {
"Contact Us"
}
Dialog.Portal() {
Dialog.Overlay(class'="modal-backdrop")
Dialog.Content(class'="modal-box") {
Dialog.Header() {
Dialog.Title(class'="text-lg font-bold") { "Get in Touch" }
Dialog.CloseButton(class'="btn btn-sm btn-circle btn-ghost")
}
form(class'="space-y-4") {
div(class'="form-control") {
label(class'="label") {
span(class'="label-text") { "Email" }
}
input(type'="email", class'="input input-bordered", required=true)
}
div(class'="form-control") {
label(class'="label") {
span(class'="label-text") { "Message" }
}
textarea(class'="textarea textarea-bordered", rows=5, required=true)
}
button(type'="submit", class'="btn btn-primary") { "Send Message" }
}
}
}
}
Motion brings animation capabilities that would enhance static-to-dynamic transitions. The design envisions smooth animations that respect user preferences for reduced motion:
// Proposed animation integration
Motion.div(
initial = {| opacity = 0; y = 20 |}
animate = {| opacity = 1; y = 0 |}
transition = {| duration = 0.3 |}
class' = "card"
) {
article(class'="prose") { content }
}
TanStack Table would provide enterprise-grade data tables for documentation and API references. The integration would leverage DaisyUI’s table styling:
// Proposed data table integration
[<SolidComponent>]
let APIReference (endpoints: APIEndpoint list) =
div(class'="overflow-x-auto") {
table(class'="table table-zebra") {
thead() {
tr() {
th() { "Method" }
th() { "Path" }
th() { "Description" }
}
}
tbody() {
For(each = endpoints) {
yield fun endpoint _ ->
tr() {
td(class'="badge badge-outline") { endpoint.method }
td(class'="font-mono text-sm") { endpoint.path }
td() { endpoint.description }
}
}
}
}
}
Why This Architecture Matters for Victor
This ecosystem design would transform what’s possible in static site and MPA architecture, transforming the decision from a hard choice to a spectrum of options. Traditional SSGs struggle with component integration - each library requires custom wrapper code and mental context switching. Victor’s design leverages Partas.Solid’s uniform bindings to make sophisticated interactivity as simple as HTML while maintaining consistent styling through DaisyUI and Tailwind CSS.
The Context: Where Victor Fits
The SPEC stack - SolidJS, Partas.Solid (with its many bindings), Elmish, CloudflareFS - represents a cohesive approach to F# web development. Within this ecosystem, Victor serves as the bridge between build-time content processing and runtime interactivity. While Partas.Solid provides the uniform component syntax and SolidJS delivers fine-grained reactivity, Victor orchestrates the transformation of content into optimized static HTML with precisely placed reactive zones.
Architectural Lineage and Influences
From Jekyll to Fornax to FlightDeck
Fornax, created by Krzysztof Cieślak as part of the Ionide suite, brought Jekyll’s conventions to F# with a critical innovation: replacing Ruby plugins with F# scripts. FlightDeck, a hard fork of Fornax, shifted the philosophy from Jekyll’s liquid templates toward Hugo’s Go template patterns while maintaining the F# script architecture.
Victor inherits this scriptable generation model but adapts it specifically for the SPEC stack:
- From Fornax: F# script-based loaders and generators, Markdig integration
- From the FlightDeck experimental app: Hugo-influenced templating patterns
- From FSharp.Formatting: We’re exploring Literate programming concepts
IaC over TOML and YAML
Following Fornax’s model, Victor intends to use various types of F# scripts:
Deployment and Optimization
The deployment phase represents the final transformation where Victor’s output becomes a globally distributed application. Integration with Cloudflare Pages goes beyond simple file uploading; Victor generates the complete deployment configuration needed for smooth deployment to Cloudflare.
Cloudflare Pages Integration
Victor generates output optimized for Cloudflare Pages:
let generateForCloudflarePages (config: Config) =
// Static HTML goes to _public
generateStaticPages "_public"
// Components bundled with esbuild
bundleComponents {
entryPoints = ["./components/index.fs"]
outdir = "_public/js"
splitting = true // Code splitting for components
format = "esm" // ES modules for modern browsers
minify = true
}
// Generate _headers file for caching
generateHeaders {
"/*.html" = "Cache-Control: public, max-age=3600"
"/js/*.js" = "Cache-Control: public, max-age=31536000, immutable"
"/css/*.css" = "Cache-Control: public, max-age=31536000, immutable"
}
// Generate _redirects for URL handling
generateRedirects config.redirectRules
Optional _headers
file could implement sophisticated caching strategies. HTML pages use short cache durations to ensure content freshness. JavaScript and CSS files, versioned through content hashing, use immutable caching for optimal performance. The _redirects
file handles URL normalization, legacy path support, and geographic routing rules.
Bundle Optimization
Components could also be optimized for minimal runtime overhead:
// Analyze component usage to optimize bundles
let analyzeComponentUsage (pages: Page list) =
pages
|> List.collect (fun page -> page.dynamicZones)
|> List.groupBy (fun zone -> zone.component)
|> List.map (fun (component, zones) ->
{| Component = component
UsageCount = zones.Length
TotalSize = getComponentSize component
ShouldInline = zones.Length > 10 |}) // Inline frequently used
Frequently used components inline into the main bundle to avoid additional network requests. Rarely used components remain as separate chunks, loaded only when needed. This analysis runs during build time, creating optimal bundle configurations without manual intervention.
Proposed Design Patterns
Victor’s flexibility combined with Partas.Solid’s ecosystem would enable architectural patterns that address complex requirements in modern web development. These patterns demonstrate how static generation and dynamic components could complement each other in a cohesive design model.
Blog with Comments
The canonical use case for selective hydration would be static posts with dynamic comment sections:
// Proposed component for comments using Kobalte for accessibility
[<SolidComponent>]
let CommentSection (postId: string) =
let comments, setComments = createSignal<Comment list>([])
onMount(fun () ->
fetch($"/api/comments/{postId}")
|> Promise.bind (fun res -> res.json())
|> Promise.iter setComments
)
div(class'="space-y-4") {
Show(
when' = fun () -> comments().Length > 0,
fallback = fun () ->
div(class'="alert alert-info") {
span() { "No comments yet. Be the first to share your thoughts!" }
}
) {
For(each = comments) {
yield fun comment _ ->
div(class'="card bg-base-100 shadow-sm") {
div(class'="card-body") {
div(class'="flex items-start space-x-3") {
div(class'="avatar placeholder") {
div(class'="bg-neutral-focus text-neutral-content rounded-full w-8") {
span() { comment.author.[0].ToString() }
}
}
div(class'="flex-1") {
p(class'="font-semibold") { comment.author }
p(class'="text-sm opacity-60") { comment.date }
p(class'="mt-2") { comment.text }
}
}
}
}
}
}
// Accessible comment form using DaisyUI styling
form(class'="card bg-base-200") {
div(class'="card-body") {
div(class'="form-control") {
label(class'="label") {
span(class'="label-text") { "Add your comment" }
}
textarea(
class'="textarea textarea-bordered",
placeholder="Share your thoughts...",
rows=4,
required=true
)
}
div(class'="card-actions justify-end") {
button(type'="submit", class'="btn btn-primary") {
"Post Comment"
}
}
}
}
}
Documentation with Interactive Examples
The design envisions interactive code samples in documentation that provide immediate feedback:
// Proposed interactive documentation component
[<SolidComponent>]
let InteractiveExample (code: string) =
let output, setOutput = createSignal("")
let isRunning, setIsRunning = createSignal(false)
div(class'="mockup-code") {
pre() {
code(class'="language-fsharp") { code }
}
div(class'="mt-4") {
button(
class'="btn btn-sm btn-primary",
onClick = fun _ ->
setIsRunning(true)
// Would compile and run F# code
tryCompileAndRun code
|> Promise.map (fun result ->
setOutput(result)
setIsRunning(false)
)
) {
Show(
when' = isRunning,
fallback = fun () -> "Run Example"
) {
span(class'="loading loading-spinner loading-sm")
span() { "Running..." }
}
}
}
Show(when' = fun () -> output().Length > 0) {
div(class'="alert alert-success mt-4") {
pre() { output() }
}
}
}
Enterprise Dashboard
Complex dashboards would combine multiple Partas.Solid bindings with DaisyUI’s comprehensive component system:
// Proposed dashboard layout
[<SolidComponent>]
let Dashboard() =
div(class'="drawer lg:drawer-open") {
input(id="drawer-toggle", type'="checkbox", class'="drawer-toggle")
div(class'="drawer-content") {
// Page content
div(class'="navbar bg-base-100 lg:hidden") {
label(for'="drawer-toggle", class'="btn btn-square btn-ghost") {
// Hamburger icon
}
}
main(class'="p-4 space-y-4") {
// Stats cards using DaisyUI
div(class'="stats shadow") {
div(class'="stat") {
div(class'="stat-title") { "Total Users" }
div(class'="stat-value") { "89,400" }
div(class'="stat-desc") { "21% more than last month" }
}
div(class'="stat") {
div(class'="stat-title") { "Page Views" }
div(class'="stat-value") { "2.6M" }
div(class'="stat-desc") { "14% more than last month" }
}
}
// Data table with TanStack Table integration
div(class'="card bg-base-100 shadow-xl") {
div(class'="card-body") {
h2(class'="card-title") { "Recent Activity" }
div(id="activity-table", ``data-component``="ActivityTable")
}
}
}
}
div(class'="drawer-side") {
label(for'="drawer-toggle", class'="drawer-overlay")
aside(class'="bg-base-200 w-64 min-h-full") {
// Sidebar navigation using DaisyUI menu
ul(class'="menu p-4 text-base-content") {
li() { a(href="/dashboard") { "Dashboard" } }
li() { a(href="/analytics") { "Analytics" } }
li() { a(href="/users") { "Users" } }
li() { a(href="/settings") { "Settings" } }
}
}
}
}
This dashboard would start as a static HTML shell for instant loading, then progressively enhance with interactive components - all styled consistently with DaisyUI’s design system.
Form-Heavy Applications
ModularForms integration enables complex form validation while maintaining type safety:
[<SolidComponent>]
let RegistrationForm() =
use form = ModularForms.createForm<RegistrationData>({
initialValues = {
email = ""
password = ""
confirmPassword = ""
acceptTerms = false
}
validate = fun values ->
let errors = ResizeArray()
if not (Regex.IsMatch(values.email, @"^[\w\.-]+@[\w\.-]+\.\w+$")) then
errors.Add("email", "Invalid email address")
if values.password.Length < 8 then
errors.Add("password", "Password must be at least 8 characters")
if values.password <> values.confirmPassword then
errors.Add("confirmPassword", "Passwords do not match")
if not values.acceptTerms then
errors.Add("acceptTerms", "You must accept the terms")
errors.ToArray()
})
Form.Root(of'=form, onSubmit=handleRegistration) {
Form.Field(of'=form, name="email") {
Form.Label(for'="email") { "Email Address" }
Form.Input(
type'="email",
required=true,
autocomplete="email"
)
Form.FieldError()
}
Form.Field(of'=form, name="password") {
Form.Label(for'="password") { "Password" }
Form.Input(
type'="password",
required=true,
autocomplete="new-password"
)
Form.FieldError()
Form.Description() { "Minimum 8 characters" }
}
// Animated submit button with loading state
Motion.button(
type'="submit",
disabled = form.isSubmitting,
whileHover = {| scale = 1.05 |}
whileTap = {| scale = 0.95 |}
) {
Show(
when' = form.isSubmitting,
fallback = fun () -> text "Create Account"
) {
Lucide.Loader2(class'="animate-spin")
span() { "Creating..." }
}
}
}
The form validates on both client and server, provides accessible error messages, and includes loading states - all while maintaining F# type safety through ModularForms bindings.
Data Gathering] HUGO --> TRANSFORM[Template Transform
Hugo → Partas.Solid] LOADER --> GEN[F# Generators
Page Assembly] TRANSFORM --> GEN GEN --> HTML[Static HTML
with Mount Points] FSX --> COMPONENTS[Partas.Solid
Components] COMPONENTS --> BUNDLE[JS Bundle
via Fable] end subgraph "Deploy Time" HTML --> PAGES[Cloudflare Pages
Static Hosting] BUNDLE --> PAGES PAGES --> CDN[Global Edge
Distribution] end subgraph "Runtime" CDN --> BROWSER[Browser
Initial Load] BROWSER --> HYDRATE[Selective Hydration
Component Mounting] HYDRATE --> INTERACTIVE[Interactive Zones
SolidJS Reactivity] end style MD fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style COMPONENTS fill:#fff3e0,stroke:#ef6c00,stroke-width:2px style CDN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
The architecture deliberately separates concerns across three phases: build-time content processing, deployment optimization, and runtime interactivity. This separation allows each phase to be optimized independently while maintaining a unified development experience through F#’s type system.
The F# Script Engine
At the core of Victor intends to provide an F# script evaluation engine that processes content through distinct script types. This approach, inherited from Fornax and later FlightDeck, aims to provide flexibility while maintaining type safety.
Loaders: Data Aggregation
Loaders gather data from any source accessible to F#. Unlike traditional static site generators that limit data sources to markdown and YAML files, Victor’s loaders can pull from databases, REST APIs, GraphQL endpoints, or F# type providers. This flexibility becomes particularly powerful when combined with CloudflareFS’s data services.
// loaders/contentloader.fsx
#r "nuget: FSharp.Data"
#r "nuget: CloudFlare.D1"
open System.IO
open FSharp.Data
open CloudFlare.D1
type ContentItem = {
slug: string
title: string
publishDate: DateTime
content: string
hasComments: bool
viewCount: int64
}
let loader (projectRoot: string) (siteContent: SiteContents) = async {
// Load markdown files
let markdownPosts =
Directory.GetFiles(Path.Combine(projectRoot, "content"), "*.md", SearchOption.AllDirectories)
|> Array.map (fun path ->
let frontMatter = Markdig.Yaml.ParseFrontMatter(File.ReadAllText(path))
{ slug = Path.GetFileNameWithoutExtension(path)
title = frontMatter.["title"] :?> string
publishDate = frontMatter.["date"] :?> DateTime
content = frontMatter.content
hasComments = frontMatter.TryGetValue("comments") |> Option.defaultValue false
viewCount = 0L })
// Augment with view counts from Cloudflare D1
let! viewCounts =
D1.query<{| slug: string; count: int64 |}>
"SELECT slug, view_count as count FROM page_analytics"
let enrichedPosts =
markdownPosts
|> Array.map (fun post ->
let viewData = viewCounts |> Array.tryFind (fun v -> v.slug = post.slug)
{ post with viewCount = viewData |> Option.map (fun v -> v.count) |> Option.defaultValue 0L })
enrichedPosts |> Array.iter siteContent.Add
return siteContent
}
Generators: Page Assembly
Generators transform the aggregated data into HTML pages. Unlike traditional template engines that operate on strings, Victor’s generators would work with Partas.Solid’s typed component model, producing HTML with embedded mount points for reactive components.
// generators/post.fsx
#r "nuget: Partas.Solid"
#load "../loaders/contentloader.fsx"
open Html
open Partas.Solid
let generate (siteContent: SiteContents) (projectRoot: string) (page: string) =
let posts = siteContent.TryGetValues<ContentLoader.ContentItem>() |> Option.defaultValue Seq.empty
let currentPost = posts |> Seq.find (fun p -> p.slug = Path.GetFileNameWithoutExtension(page))
html() {
head() {
title() { currentPost.title }
meta(name="description", content=currentPost.title)
link(rel="stylesheet", href="/css/style.css")
}
body() {
article(class'="prose mx-auto") {
header() {
h1() { currentPost.title }
time(datetime=currentPost.publishDate.ToString("o")) {
currentPost.publishDate.ToString("MMMM d, yyyy")
}
// Dynamic view counter mount point
div(id="view-counter", ``data-post-id``=currentPost.slug, ``data-initial-count``=string currentPost.viewCount)
}
// Static content
div(class'="content") {
RawHtml(Markdig.Markdown.ToHtml(currentPost.content))
}
// Conditional comment section mount point
if currentPost.hasComments then
div(id="comment-section", ``data-post-id``=currentPost.slug)
}
}
}
|> HtmlElement.ToString
The generator produces a complete HTML page with static content for immediate display and mount points for dynamic components. This approach ensures optimal Time-to-First-Byte while maintaining interactivity where needed.
Template Transformation: From Hugo to Partas.Solid
One of Victor’s key innovations is its ability to transform existing Hugo templates into Partas.Solid components. This transformation preserves the semantic structure and design decisions embedded in Hugo themes while enabling type-safe component composition.
The transformation process operates in three stages. First, Hugo’s Go template syntax is parsed into an abstract syntax tree. Second, template constructs are mapped to equivalent F# expressions - {{ range .Pages }}
becomes For(each = pages)
, {{ partial "header" . }}
becomes a function call to a Partas.Solid or Kobalte component. Third, the resulting F# code is emitted with proper dependencies, type annotations and component structure.
// Hugo template
{{ define "main" }}
<article class="post">
<h1>{{ .Title }}</h1>
<time>{{ .Date.Format "January 2, 2006" }}</time>
{{ .Content }}
{{ if .Params.comments }}
{{ partial "comments" . }}
{{ end }}
</article>
{{ end }}
// Transforms to Partas.Solid component
[<SolidComponent>]
let PostTemplate (post: Post) =
article(class'="post") {
h1() { post.Title }
time() { post.Date.ToString("MMMM d, yyyy") }
RawHtml(post.Content)
Show(when' = fun () -> post.Params.comments) {
CommentsPartial(post = post)
}
}
This transformation maintains the template’s intent while providing F#’s type safety and IntelliSense support. Designers can continue working with familiar Hugo site designs while benefitting from compile-time structural verification.
Integration with CloudflareFS
At deployment, Victor generates the necessary configuration for Cloudflare Pages, including _headers
files for caching policies and _redirects
for URL management. The tool also bundles Worker functions for API endpoints that dynamic components might need, ensuring a complete deployment package.
let deployToCloudflare (config: VictorConfig) = async {
// Generate static pages
let! pages = generatePages config
// Optimize assets
let! optimized = optimizeAssets {
images = compressImages pages.images
styles = bundleCSS pages.styles
scripts = bundleJS pages.scripts
}
// Generate Cloudflare configuration
let headers = generateHeaders {
"/*.html" = "Cache-Control: public, max-age=3600"
"/js/*.js" = "Cache-Control: public, max-age=31536000, immutable"
"/css/*.css" = "Cache-Control: public, max-age=31536000, immutable"
}
let redirects = generateRedirects config.urlMappings
// Deploy to Pages
let! deployment = CloudflareFS.Pages.deploy {
projectName = config.siteName
directory = config.outputDir
headers = headers
redirects = redirects
functions = config.workerFunctions
}
return deployment
}
Progressive Enhancement Strategies
Victor would create the scaffolding for the SPEC stack to express sophisticated progressive enhancement strategies that respect both user experience and resource constraints. Components are not uniformly hydrated; instead, they follow configurable strategies based on their importance and user interaction patterns.
The Intersection Observer API enables lazy loading of components as they enter the viewport. This approach proves particularly effective for comment sections, related article widgets, and media players that appear below the fold. Components marked with hydration="OnVisible"
remain inert until needed, reducing initial JavaScript execution.
// Generated by Victor for progressive enhancement
const componentRegistry = new Map([
['ViewCounter', () => import('./components/ViewCounter.js')],
['CommentSection', () => import('./components/CommentSection.js')],
['MediaPlayer', () => import('./components/MediaPlayer.js')]
]);
const hydrationStrategies = {
immediate: (element, Component) => {
render(() => Component(parseProps(element)), element);
},
onVisible: (element, Component) => {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
render(() => Component(parseProps(element)), element);
observer.disconnect();
}
}, { rootMargin: '50px' });
observer.observe(element);
},
onIdle: (element, Component) => {
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
render(() => Component(parseProps(element)), element);
});
} else {
setTimeout(() => {
render(() => Component(parseProps(element)), element);
}, 1);
}
}
};
This granular control over hydration ensures that static content displays immediately while interactive features can be configured to load progressively, creating observable performance that exceeds both the pre-load lag in pure static sites and janky transitions with traditional SPAs.
Development Workflow
Victor’s development experience intends to center on rapid iteration with immediate feedback. A planned watch mode, inherited from FlightDeck (and Fable), monitors the file system for changes and triggers incremental rebuilds. Unlike full site regeneration, Victor should track dependencies between loaders, generators, and content files to rebuild only affected pages. This is still an area of some measured optimism but we want to emphasize the intent to create an interactive experience with full Cloudflare bindings.
The local Worker “server” infrastructure is still a developing story with Cloudflare making significant advances in multi-Worker local deployments for development and testing. We expect our interactive build monitoring to include live reload. When components change, we would expect that only the JavaScript bundle rebuilds. When content changes, only affected pages regenerate. This granular approach maintains sub-second feedback loops even for large sites, but will take some work to fully realize.
Real-World Patterns
Victor’s intended flexibility should enables several architectural patterns that address common requirements in modern web development.
Documentation Sites with Interactive Examples
Technical documentation benefits from static content for fast loading and SEO while requiring interactive code examples and API explorers. Victor handles this duality through selective component mounting.
// Generates mount point for Monaco editor with F# support
[<SolidComponent>]
let CodeExample (code: string) (language: string) =
let output, setOutput = createSignal("")
let error, setError = createSignal("")
div(class'="code-example") {
MonacoEditor(
value = code,
language = language,
theme = "vs-dark",
onChange = fun newCode ->
match FSharp.Compiler.Service.tryCompileAndRun newCode with
| Ok result -> setOutput(result)
| Error err -> setError(err)
)
Show(
when' = fun () -> output().Length > 0,
fallback = fun () ->
Show(when' = fun () -> error().Length > 0) {
pre(class'="error") { error() }
}
) {
pre(class'="output") { output() }
}
}
E-commerce Product Catalogs
Product pages require immediate content display for SEO while needing dynamic features like inventory checking, price updates, and shopping cart integration. Victor generates static product pages with mount points for these dynamic elements.
// Product page generator
let generateProductPage (product: Product) =
let staticPrice = product.basePrice
let staticInventory = if product.inStock then "In Stock" else "Out of Stock"
article(class'="product", itemscope=true, itemtype="https://schema.org/Product") {
// Static content for SEO
h1(itemprop="name") { product.name }
div(class'="price", itemprop="offers", itemscope=true, itemtype="https://schema.org/Offer") {
span(itemprop="price", content=string staticPrice) { $"${staticPrice}" }
meta(itemprop="priceCurrency", content="USD")
link(itemprop="availability", href=if product.inStock then "https://schema.org/InStock" else "https://schema.org/OutOfStock")
}
// Dynamic price updater mount point
div(id="dynamic-price", ``data-product-id``=product.id, ``data-base-price``=string staticPrice)
// Dynamic inventory checker mount point
div(id="inventory-status", ``data-product-id``=product.id, ``data-initial-stock``=staticInventory)
// Add to cart component mount point
div(id="add-to-cart", ``data-product-id``=product.id)
}
Multi-tenant SaaS Applications
SaaS platforms often need public-facing marketing pages (static) alongside authenticated application interfaces (dynamic). Victor generates the marketing site as static HTML while preparing mount points for authentication widgets and feature demos.
Performance Characteristics
Victor’s architecture will target measurable performance improvements across multiple dimensions. In many ways it’s a matter of “taking the easy wins.” Initial page loads benefit from static HTML delivery, achieving Time-to-First-Byte under 50ms when served from Cloudflare’s edge network. The selective hydration approach reduces JavaScript execution time by 60-80% compared to full SPA hydration.
Bundle sizes remain manageable through code splitting and tree shaking. A typical site with ten unique components generates a base bundle of approximately 15KB (gzipped) for SolidJS runtime plus 2-5KB per component. This compares favorably to React-based solutions that start at 45KB for the runtime alone.
Build times scale linearly with content volume rather than site complexity. Hugo’s “story” for this is the benchmark, but with Cloudflare-deployed services that are bound to local workers there will be some tooling and measurement to identify advantages and challenges. The goal is to enable rapid iteration during development while maintaining reasonable CI/CD times when migrating to managed environments.
Integration with the SPEC Stack
Within the broader SPEC stack, Victor serves as the orchestration layer that brings together the various components. Partas.Solid components authored for dynamic applications work seamlessly as static site components. Elmish state management integrates through Victor’s component mounting strategy, enabling complex state machines in otherwise static pages. CloudflareFS provides the deployment target and runtime services that components may need.
This integration creates a unified development experience where skills and code transfer across project types. A component library developed for am MPA can be reused in a static documentation site. State management patterns proven in dynamic applications apply equally to progressive enhancement scenarios. A new continuum of working models and solution modes will emerge as this modernized approach meets Cloudflare’s serverless edge infrastructure.
Future Directions
Victor’s architecture positions it well for emerging web platform features. Partial hydration, where only interactive parts of components hydrate while static parts remain inert, could further reduce JavaScript overhead. Service Worker integration could enable offline functionality for static sites with dynamic features. Edge-side Includes might allow dynamic content injection at the CDN level, combining static generation with request-time personalization.
The Infrastructure-as-Code approach though F# build scripts could also support community extension. Custom loaders could integrate with any data source accessible to .NET. Specialized generators might target different output formats - AMP pages, email templates, or PDF documents. The component transformation pipeline could support other frameworks beyond Partas.Solid, enabling other full-stack frameworks.
Conclusion
While the idea is bold, Victor represents a pragmatic synthesis of proven approaches to web content generation. By combining Hugo’s templating philosophy with Partas.Solid’s component model and F#’s type system, it creates a development experience that respects both developer productivity and runtime performance. The tool’s design acknowledges that most web applications exist on a spectrum between purely static and fully dynamic, providing the mechanisms to place interactivity precisely where it provides value.
For teams that show interest in the SPEC stack, Victor will complete the platform story by addressing the build-time, content generation and server-side binding layers. For those evaluating alternatives to traditional static site generators, it offers a path that doesn’t sacrifice modern component models for performance. The result is a tool that fits naturally into the F# web development ecosystem while pushing forward what’s possible with modern, adaptive distributed systems design.