no-js is an opinionated Go framework for server-rendered web applications.
no-js is for apps that want:
- server-rendered pages with typed loaders and layouts
- generated route wiring from a strict file-based app tree
- metadata composition for
<head> - i18n-aware routing
- static asset fingerprinting
- optimized data streaming
- optional HTMX partial navigation support
The route generator is strict. A consuming app is expected to look like this:
your-app/ # provides the app root; required
go.mod # provides the module path for generated imports; required
internal/ # provides the framework-visible app namespace; required
web/ # provides the fixed web namespace used by the generator; required
app/ # provides the file-based route tree; required
root.templ # provides the document shell; required
404.templ # provides the root not-found page; required
error.templ # provides the root error page; required
page.templ # provides the / route page; optional unless you serve /
layout.templ # provides the root-segment layout; optional
note/ # provides a static route segment; optional
[slug]/ # provides a dynamic route segment; optional, but [param] syntax is required
page.templ # provides the /note/:slug page; required for this route
runtime/ # provides app-specific contracts for generated code; required today
context.go # provides runtime.Context; required today
view_models.go # provides runtime page view types and RootLayoutView; required today
loaders.go # provides page loaders and metadata helpers; usually required
resolvers/ # provides handwritten route resolver methods; required
root.go # provides resolver methods for the root route; optional per route
note_param_slug.go # provides resolver methods for /note/[slug]; optional per route
Generated files are written to:
internal/web/gen/ # provides generated route modules with safe Go package names
internal/web/resolvers/generated.go # provides generated route resolver interfaces and param types
These are not suggestions. They are current framework contracts.
- Routes live under
internal/web/app. - Dynamic segments must use
[param]directories. - Route templates must use the exact file names
root.templ,layout.templ,page.templ,404.templ, anderror.templ. root.templis required atinternal/web/app/root.templ.- Root
404.templand rooterror.templare required. page.templview types must beruntime.*.- Layout and error/not-found contracts currently depend on
runtime.RootLayoutView. - Generated code imports
internal/web/runtimeandinternal/web/resolversfrom the consuming module. - Route-local
components/directories are rejected by the generator. - Only
root.templmay contain document-level tags like<html>,<head>, and<body>.
-
Runtime packages under
framework/* -
framework/engineRoute execution, concurrent metadata and page loading, layout composition, and streaming root-layout rendering. -
framework/httpserverHTTP server integration, cache policies, gzip,/healthz, static asset mounting, and optional public-file middleware. -
framework/metagenCanonical URLs, alternate languages/types, robots tags, Open Graph, Twitter cards, Pinterest tags, and HTMX head patch generation. -
framework/i18nLocale config, locale-aware path handling, request locale context, and routing prefix modes:always,as-needed,never. -
framework/staticassetsRuntime manifest loading and runtime asset URL composition. -
Build-time packages under
bundler/* -
bundler/approutegenRoute discovery and generated registry/resolver contracts from the file tree. -
bundler/i18nkeygenGo key generation from canonical locale message definitions. -
bundler/staticassetsMinification, hashing, manifest generation, and versioned asset bundle assembly under/_assets/. -
bundler/templgentemplgeneration for selected files or paths. -
CLI entrypoints under
cmd/* -
cmd/*CLI entrypoints for route generation,templgeneration, i18n key generation, and static asset building.
- This framework is intentionally not generic yet. The generator still assumes an
runtimepackage and specific template signatures. - The generator is module-aware: framework imports point to
github.com/RevoTale/no-js, but generated app imports are resolved from the consuming app'sgo.mod. - i18n locales are currently normalized to two-letter lowercase codes.
- HTMX support is request-driven. Partial requests are detected through
HX-Request, and metadata patches are emitted through response headers. - Build-time packages may import runtime-owned shared types, but runtime packages must not import bundler packages.
- Static assets and public files are separate concerns:
/_assets/is for fingerprinted build output, while public files are served as fixed root-level paths.
task fix
task validate
task testno-js originated as an extraction from RevoTale/blog.