Skip to content

Releases: dev2k6/Nguyen.go

v1.4.0

16 May 18:50

Choose a tag to compare

v1.4.0 — .live.gox Codegen, Effect Wall, Reattach Wire-up

Released: 2026-05-17

v1.4.0 closes the gaps between v1.3.0's release notes and its actual
implementation, ships the .live.gox codegen that was the primary
v1.3.1 roadmap item, and adds the first Effect Wall rules to
nguyen vet.


Highlights

.live.gox codegen — zero-boilerplate Live pages

Write a Live page as a single file. No RegisterLivePage call, no
separate Go struct file:

---
var Count int

func inc(state *CounterState) { state.Count++ }
func dec(state *CounterState) { state.Count-- }
---

<section>
  <h1>Count: {Count}</h1>
  <button @click="dec()">-</button>
  <button @click="inc()">+</button>
</section>

Save as pages/counter.live.gox. The compiler:

  1. Detects File.IsLive (.live.gox suffix or //+nguyen:live directive)
  2. Calls parser.TranspileLive — generates a livepage.Page implementation
  3. Writes the Go source to <buildDir>/live/<name>.go
  4. Skips TinyGo entirely — no WASM compilation for live pages

The server scans <buildDir>/live/ on startup and registers each page
automatically.

Session reattach — wired end-to-end

Hub.Reattach was implemented in v1.3.0 but never called by the
WebSocket handler. v1.4.0 completes the wire-up:

  • readLoop now checks evt.Kind == "reattach" before dispatching
  • On valid token: swaps the session reference, logs live.session.reattach
  • On expired token: sends {t:"error"} and closes the connection
  • tokenIndex is kept alive after sess.Close() so reconnects within
    IdleTimeout always find the session

Live stats in /_nguyen/health

{
  "status": "ok",
  "version": "1.4.0",
  "live": {
    "sessions": 42,
    "dropped_messages": 0
  }
}

HealthHandler now accepts a *live.Hub and exposes session count
and cumulative dropped message count.

WithHubOptions on pkg/nguyen.App

Hub tuning is now fully exposed:

app := nguyen.New(
    nguyen.WithHubOptions(live.Options{
        MaxSessions:    50_000,
        IdleTimeout:    90 * time.Second,
        OutboundBuffer: 32,
    }),
)

Previously live.NewHub was always called with empty options.


Effect Wall — first live-aware vet rules

nguyen vet gains two new rules for .live.gox files:

live-render-pure — flags DB, HTTP, and FS calls inside Render
methods. These belong in Init or Handle:

pages/dashboard.live.gox:14: error [live-render-pure]
  Render calls "db." — move side effects to Init or Handle

live-handler-returns-state — flags handler functions bound via
@event that have no return statement. Live handlers must return
the (possibly mutated) state:

pages/counter.live.gox:8: error [live-handler-returns-state]
  handler "inc" in .live.gox has no return statement — it must return the new state

These are the first rules that use go/ast to inspect .live.gox
frontmatter with live-mode awareness, forming the foundation of the
Effect Wall.


Bug fixes / correctness

  • tokenIndex is no longer cleared on sess.Close() — only on reap
    or successful reattach. This was the root cause of reattach always
    returning ErrTokenExpired in v1.3.0.
  • TestReattach and TestDroppedMessagesCounter added to
    internal/live/live_test.go.

Compatibility

Fully backward compatible with v1.3.x.

New public API:

  • nguyen.WithHubOptions(live.Options) — new option function
  • server.HealthHandler(*live.Hub) — signature change (nil-safe; pass
    nil for the no-hub case used by internal/server/fiber_app.go)
  • parser.TranspileLive(*File) *LiveComponentInfo — new function
  • parser.LiveComponentInfo — new type
  • vet.DefaultRules() — two new rules added

What's next

v1.5 will land:

  • Telegram Protocol — typed streaming wire format for SSR with
    out-of-order rendering and per-frame error recovery
  • Parallel routes — render multiple independent route segments
    simultaneously (Next.js @slot pattern)
  • Spore — component-as-process model with bounded mailboxes and
    supervision trees

Install

go install github.com/dev2k6/Nguyen.go/cmd/nguyen@v1.4.0
go get github.com/dev2k6/Nguyen.go@v1.4.0

Author

Thái Nguyên thainguyen.junior@gmail.com

v1.3.0

16 May 02:55

Choose a tag to compare

v1.3.0 — Keyed Diff, Session Reattach, Bug Fixes

Released: 2026-05-16

v1.3.0 ships the Live Mode improvements roadmapped in v1.2.0, fixes
four correctness bugs that could silently break generated code, and
hardens several security and reliability edges found during codebase
review.


Highlights

Keyed reconciliation in the server-side differ

The Live Mode VDOM differ now performs O(n) keyed reconciliation when
children carry a data-key attribute. Previously any change in child
count caused a full replace of the parent element. Now only the
changed nodes are patched:

<ul>
  <li data-key="1">Alice</li>
  <li data-key="2">Bob</li>
</ul>

Adding, removing or reordering keyed children emits minimal insert
and remove ops instead of replacing the whole list. Unkeyed lists
fall back to the previous behaviour — fully backward compatible.

Persistent session reattach

Live Mode sessions now survive WebSocket reconnects. On first connect
the server issues a 32-hex-char reattach token:

{"t":"session","token":"a3f8..."}

The browser stores the token and sends it on reconnect:

{"t":"reattach","token":"a3f8..."}

Hub.Reattach(ctx, token) looks up the session, resets its transport
context, and hands the existing state back to the new connection. If
the token has expired (session was reaped after IdleTimeout) the
server returns ErrTokenExpired and the client falls back to a fresh
session gracefully.

Extended protocol event kinds

live.Event now carries three kinds beyond the original "event":

Kind New fields Use case
"form" FormData map[string]string Full form submission in one frame
"navigate" Path string Client-side navigation within a live page
"reattach" Token string Reconnect with existing session

DecodeEvent validates the Kind field against the known set and
returns an error for unknown kinds, preventing malformed frames from
reaching handlers.

WithLiveOptions on pkg/nguyen.App

Hub tuning is now exposed via the programmatic API:

app := nguyen.New(
    nguyen.WithLiveOptions(server.LiveOptions{
        AllowedOrigins: []string{"https://example.com"},
        AuthFunc: func(c *fiber.Ctx) error {
            return myAuth.ValidateSession(c)
        },
    }),
)

Dropped message observability

Session.DroppedMessages (atomic counter) increments whenever the
outbound buffer is full and a message is dropped. Hub.DroppedTotal()
aggregates across all live sessions and is surfaced in
/_nguyen/health:

{
  "live": {
    "sessions": 42,
    "dropped_messages": 0
  }
}

Bug fixes

ng-for unused index variable — compile error

<ng-for> without an index attribute previously emitted
for i, item := range items where i was never used, causing a Go
compile error in every generated WASM file that used ng-for. Fixed:
the loop now emits for _, item := range items when index is absent.

Named slot regex — multi-line content silently dropped

replaceNamedSlots used (.*?) without the (?s) flag, so any slot
content spanning more than one line was silently discarded. Fixed with
(?s)(.*?).

tokenizeHTML<script>/<style> content corrupted

The transpiler's HTML tokenizer treated < inside <script> and
<style> blocks as tag opens, corrupting the parse tree. Fixed: after
opening a raw-text tag the tokenizer now scans forward to the matching
close tag and emits the inner content as a single text token.

WASM reconciler — event delegation not wired

mountToDOM in pkg/core/reconciler.go silently skipped onClick,
onChange and other on* props with a bare continue. Fixed: these
props are now written as data-nguyen-{event} attributes so the
existing SetupEventDelegation listener picks them up correctly.


Security / correctness

Item Detail
WrapHTML locale Accepts a locale string param; no longer hardcodes lang="en". Callers pass the i18n locale from request context.
CSP nonce injectHydrationMarkers accepts a nonce string; emits <script nonce="..."> when non-empty. Pass c.Locals("csp_nonce") from your middleware.
pathToHash Extended from 48-bit (12 hex chars) to 64-bit (16 hex chars), eliminating birthday collision risk at scale.
RevalidateTag Now calls BackgroundRevalidate instead of the synchronous RevalidatePath, so a tag shared by many pages no longer blocks the caller.
ISR Lifecycle NewISRWithContext(ctx, dir) integrates the cleanup goroutine with pkg/concurrent.Lifecycle. NewISR remains as a backward-compatible wrapper.

Compatibility

Fully backward compatible with v1.2.x. The only public API additions are:

  • live.ErrTokenExpired — new sentinel error
  • live.Event.FormData, live.Event.Path, live.Event.Token — new fields (zero-value safe)
  • live.Outbound.Token — new field
  • live.Hub.Reattach(ctx, token) — new method
  • live.Hub.DroppedTotal() — new method
  • live.Session.DroppedMessages — exported atomic field
  • live.Session.ReattachToken — exported field
  • render.WrapHTML(body, result, locale) — added locale param (pass "" for "en")
  • render.injectHydrationMarkers — internal, nonce param (empty = no nonce)
  • cache.NewISRWithContext(ctx, dir) — new constructor

What's next

v1.3.1 will land:

  • .live.gox codegen — a single file with frontmatter struct + methods
    • HTML template becomes a registered livepage.Page automatically.
      The parser already sets File.IsLive = true; the compiler pass is
      the remaining work.
  • Streaming partial renders (Telegram Protocol foundation).

Install

go install github.com/dev2k6/Nguyen.go/cmd/nguyen@v1.3.0
go get github.com/dev2k6/Nguyen.go@v1.3.0

Author

Thái Nguyên thainguyen.junior@gmail.com

v1.2.2

15 May 06:31

Choose a tag to compare

v1.2.2 — Security Patch (Medium/Low)

Released: 2026-05-15

Follow-up security patch addressing the medium and low severity
findings from the v1.2.0 audit. No API changes; fully backward
compatible with v1.2.x.

Security fixes

Medium: Image decompression bomb DoS

File: internal/optimizer/image.go

DecodeImage called image.Decode directly, which decompresses the
full image into memory before any size check. A crafted JPEG/PNG
(e.g. 10000×10000 pixels at 100 KB compressed → 400 MB in RAM) could
exhaust server memory.

Fix: DecodeImage now calls image.DecodeConfig first to read
only the image header. If either dimension exceeds 8192 pixels the
function returns an error without decoding the full image. The limit
is configurable via the maxImageDimension constant.

Medium: S3 upload io.ReadAll OOM

File: internal/upload/s3.go

S3Storage.Put called io.ReadAll(reader) without a size limit.
A client that declared a small Content-Length but sent a large body
could exhaust server memory.

Fix: The reader is now wrapped with io.LimitReader(reader, size+1)
before io.ReadAll. If the actual body exceeds the declared size the
upload is rejected with an error. When size is unknown (≤ 0) a
32 MB default cap is applied.

Medium: CSRF cookie missing explicit SameSite documentation

File: internal/csrf/csrf.go

The CSRF cookie already defaulted to SameSite=Lax but the code
comment did not explain why HTTPOnly: false is intentional (JS must
read the token for the double-submit pattern). Added a clear comment
to prevent future "fix" PRs from accidentally setting HTTPOnly: true.

Medium: OAuth state cookie missing SameSite attribute

File: internal/auth/oauth.go

The OAuth state cookie had no SameSite attribute, leaving it
unprotected against CSRF attacks on the OAuth callback endpoint.

Fix: SameSite: "Lax" is now set on the oauth_state cookie.
Lax is the correct value here: the cookie must be sent when the
OAuth provider redirects back (a top-level GET navigation), but not
on cross-site sub-resource requests.

Low: concurrent.Pool.Submit channel leak on close race

File: pkg/concurrent/concurrent.go

Between the two select statements in Submit, the pool could close
after the reply channel was created but before it was sent to the
jobs queue. The channel would never be drained, leaking a small
allocation. Added a comment documenting the intent and confirming the
buffered channel (cap 1) means no goroutine blocks.

Low: nguyen vet secret detection rules too narrow

File: internal/vet/vet.go

The previous patterns required ≥ 12 characters and missed:

  • Short JWT secrets (< 12 chars but still dangerous)
  • Common service key prefixes (sk_live_, AKIA, SG.)
  • Private key PEM headers
  • Empty string assignments to known secret variable names

Fix: Five patterns now cover the above cases. The minimum length
for generic key/password patterns is reduced to 8 characters.

Upgrade

go get github.com/dev2k6/Nguyen.go@v1.2.2

No configuration changes required. The image dimension limit (8192)
and S3 default cap (32 MB) are conservative defaults that cover all
legitimate use cases. If you serve very large images, increase
maxImageDimension in internal/optimizer/image.go or pre-process
images before passing them to the optimizer.

Author

Thái Nguyên thainguyen.junior@gmail.com

v1.2.0

15 May 05:36

Choose a tag to compare

v1.2.0 — Live Mode

Released: 2026-05-15

Live Mode renders an entire page on the server, opens a WebSocket back
to the same route, and pushes a small VDOM patch each time state
changes. Interactive UI ships with 0 KB of compiled Go in the
browser
— no TinyGo, no WASM bundle, no client-side state library.

The architecture takes advantage of two Go primitives JavaScript runtimes
cannot match: cheap goroutines (one per WebSocket connection) and
context.Context cancellation that automatically cleans up every
in-flight DB query, timer and helper when a session ends. Phoenix
LiveView demonstrated this pattern for Elixir; v1.2.0 brings it
natively to Go without copying its API or jargon.

Highlights

pkg/live and pkg/livepage — public Live Mode contract

// livepage.Page is the contract for a live route.
type Page interface {
    Init(ctx context.Context, params map[string]string) (state any, err error)
    Render(ctx context.Context, state any) (string, error)
    Handle(ctx context.Context, state any, evt live.Event) (any, error)
}

State stays on the server, Render emits HTML, Handle reacts to one
client event. Register pages from your WithSetup hook:

app := nguyen.New(nguyen.WithPort(3000))
app.RegisterLivePage("/counter", &CounterPage{})
app.Listen("")

internal/live — Hub, Session and VDOM differ

  • live.Hub owns sessions process-wide with MaxSessions (default
    10000), IdleTimeout (60s), HeartbeatInterval (30s) and
    OutboundBuffer (16) options.
  • live.Session runs one goroutine per connection, drains an outbound
    channel, and recovers from panic in handlers (turning them into
    structured error frames so the session stays open).
  • live.Diff(prev, next) produces minimal patch ops: text, attr,
    replace, insert, remove, plus a root fallback when the tree
    shape changes too much.

/_nguyen/live.js — tiny browser runtime

A ~6 KB script auto-mounted by App.serve() discovers
<div data-nguyen-live="/route"> elements, opens a WebSocket, applies
patches and forwards events. Built-in features:

  • Event delegation for click, submit, input, change, keydown,
    keyup, focus, blur.
  • Auto-reconnect with exponential backoff (capped at 30s).
  • Server-driven heartbeat (30s ping) keeps NAT/proxy intermediaries
    from idle-closing.
  • Form submits and intra-page anchors have their default action
    prevented so the session stays alive.

.gox parser detection

A page opts into Live Mode by either:

  • file suffix .live.gox, or
  • frontmatter directive //+nguyen:live.

The parser sets File.IsLive = true. A future codegen pass (v1.3) will
turn the frontmatter struct + methods into a livepage.Page
automatically.

Wire protocol

Client → server (one inbound event):

{"t":"event","name":"increment","target":"0/1","value":null}

Server → client:

{"t":"replace","html":"<...>"}
{"t":"diff","ops":[{"op":"text","path":"0/1","value":"Count: 4"}]}
{"t":"ping"}
{"t":"error","error":"..."}

JSON is used in v1.2.0 for debuggability; binary framing is on the
roadmap once we have real deployments to benchmark against.

Example

examples/hello-nguyen ships a working Live counter:

  • pages/live-counter.gox — host page with the <div data-nguyen-live>
    mount and the <script src="/_nguyen/live.js"> tag.
  • livedemo/counter.go — implements livepage.Page for /counter.
  • cmd/server/main.go — wires the page in via app.RegisterLivePage.

Run it with:

cd examples/hello-nguyen
go run ./cmd/server

Then open http://localhost:3000/live-counter.

Documentation

  • docs/live-mode.md — full guide: architecture,
    page contract, event delegation, patch ops, hub tuning, failure modes
  • docs/agent-reference.md — Live Mode
    section so AI assistants emit the correct boilerplate
  • README.md — updated feature list

Compatibility

This release is fully backward compatible with v1.1.x. No .gox
syntax changed. Live Mode is opt-in: applications that do not call
RegisterLivePage see no extra goroutines, no extra HTTP handlers
beyond /_nguyen/live.js (served lazily) and no behavioural change.

Performance and limits

  • One Live session ≈ 10–20 KB of Go state. 10K concurrent sessions
    ≈ 100–200 MB of RAM, well within a single-VM budget.
  • Round-trip per interaction is bounded by network latency (typically
    30–100 ms over the public internet, sub-millisecond over a LAN).
  • The differ is structural; child-count changes fall back to a
    replace of the parent. Keyed reconciliation (<li :key="...">)
    is on the v1.3 roadmap.

When to use Live Mode

  • Admin panels, dashboards, internal tooling.
  • Form flows, multi-step wizards, anything mostly server-driven.
  • Pages where you want zero client-side bundle for ergonomic and
    security reasons.

Skip it for sub-frame UI (drag, canvas, animation) or offline-capable
pages — keep the existing CSR / SSR + WASM model for those.

What's next

v1.3 will land:

  • .live.gox codegen so a single file with frontmatter + template
    becomes a registered live page.
  • Keyed reconciliation in the differ.
  • Persistent session reattach (state survives reconnect).
  • Streaming partial renders (Telegram Protocol foundation).

Install

go install github.com/dev2k6/Nguyen.go/cmd/nguyen@v1.2.0
go get github.com/dev2k6/Nguyen.go@v1.2.0

Author

Thái Nguyên thainguyen.junior@gmail.com

v1.1.0

15 May 05:05

Choose a tag to compare

v1.1.0 — Foundation Primitives

Released: 2026-05-15

This release lays the foundation for the next generation of Nguyen.go.
Five Go primitives — goroutines, context.Context, go/ast, embed.FS,
and stdlib observability — are now first-class citizens of the
framework, exposed through new packages, middleware and CLI tooling.

Highlights

Concurrency primitives — pkg/concurrent

Typed wrappers around the CSP patterns Nguyen.go applications use most.

  • Pool[J, R] — bounded worker pool with typed jobs/results,
    panic recovery and ctx-aware cancellation.
  • Map — fan-out across N workers preserving input order, first
    error cancels the rest.
  • Parallel — fire-and-collect for independent loaders.
  • Race — first-success-wins across multiple sources, with
    ErrAllSourcesFailed reporting every failure.
  • Deadline — bound any function with a timeout.
  • Lifecycle — coordinate ordered start/stop of long-lived
    components (HTTP, WS, DB, cron, brokers) with stop-grace.

Request-scoped context — pkg/ngctx

Standard typed keys for request-scoped values:

  • WithTraceID / TraceID
  • WithRequestID / RequestID
  • WithLocale / Locale
  • WithUser / User (carries ID, Email, Roles, Extra)
  • WithBudget / Budget
  • FromHTTP(ctx, headers) populates trace/request/locale from a request

These keys are populated automatically by the new
server.ContextMiddleware so loaders and actions read them via
context.Context rather than Fiber's Locals map.

Structured logging + spans — pkg/observe

Built on Go 1.21+'s log/slog.

  • observe.Default() / SetDefault() — swap the root logger
    (text or JSON) at startup.
  • observe.Logger(ctx) — returns a logger automatically enriched with
    trace_id, request_id, locale, user_id from ngctx.
  • observe.NewJSON(w, level) — production-ready JSON handler for
    Datadog / Loki / GCP Logging.
  • observe.StartSpan(ctx, name) / Span.End(err) — lightweight
    timing helper that emits a structured event when ended.

The framework's HTTP server now emits one http event per request
with method, path, status, duration and trace IDs, and writes
X-Trace-Id / X-Request-Id response headers.

nguyen doctor

Pre-flight diagnostics for the host environment and project layout.
Reports Go version, TinyGo availability, Tailwind binary, presence of
pages/, public/, styles/, config/nguyen.config.yml, and git
status. Exits non-zero only on hard prerequisites — safe for CI.

nguyen vet

Static analysis for .gox files. Six rules ship in the box:

Rule Severity
context-first-param warning
no-time-sleep warning
goroutine-cancellation warning
no-hardcoded-secrets error
no-fmt-print warning
event-handler-exists error

--strict promotes warnings to errors for CI gating. The rule engine is
the seed of the future Effect Wall.

nguyen generate routes

Reads the pages directory, sorts routes deterministically and emits a
typed Go file with one accessor per route. Static routes return
constants; dynamic routes accept typed parameters.

<a href={routes.BlogBySlug(post.Slug)}>{post.Title}</a>

A typo becomes a compile error rather than a 404 at runtime. The
generated file is byte-identical across machines, so it diffs cleanly
in code review and works inside reproducible builds.

nguyen dist

Cross-compile reproducible single-binary releases.

nguyen dist --targets linux/amd64,linux/arm64,darwin/arm64

When --reproducible (default on), dist sets -trimpath,
-buildvcs=false, strips symbols and the build ID, and disables CGo.
Two builds from the same source on different machines produce
byte-identical binaries — supply-chain verifiable, air-gap deployable.

Documentation

Three new guides:

docs/cli.md and the README are extended with the new
commands.

Compatibility

This release is fully backward compatible with v1.0.x. No .gox
syntax changed. All new packages live under pkg/concurrent,
pkg/ngctx, pkg/observe and do not affect existing imports.

The new ContextMiddleware and AccessLog are wired into the default
Fiber stack. If you were calling .Fiber().Use(...) to register your
own logging middleware, both stacks coexist without conflict, but you
may want to disable yours and read observe.Logger(ctx) instead.

What's next

v1.1.0 is the first half of the Foundation Primitives series. v1.2.0
will land the second half:

  • Spore — component-as-process model with bounded mailboxes and
    supervision trees
  • Effect Wall — full effect-class inference using go/types so
    build failures replace runtime "DB call from client component"
    surprises
  • Telegram Protocol — typed streaming wire format for SSR with
    out-of-order rendering and per-frame error recovery

Install

go install github.com/dev2k6/Nguyen.go/cmd/nguyen@v1.1.0

Or upgrade an existing project:

go get -u github.com/dev2k6/Nguyen.go@v1.1.0

Author

Thái Nguyên thainguyen.junior@gmail.com

v1.0.7

14 May 04:25

Choose a tag to compare

What's New

Nguyen.go v1.0.7 adds a full range of backend modules, transforming the framework into a full-stack solution for all types of web applications.

New Modules

  • Database — Connection pool, query builder (SELECT/INSERT/UPDATE/DELETE), transaction support
  • Migration — SQL migration system với CLI (nguyen migrate up/down/create/status)
  • Auth — JWT (HS256), refresh token, bcrypt password hashing, session store, OAuth2 (Google, GitHub, Facebook)
  • Auth MiddlewareRequired(), Role("admin"), Optional()
  • CSRF — Token generation, constant-time validation, auto-rotate, configurable skip paths
  • Validator — 13+ built-in rules, struct tag validation, map validation, Fiber middleware integration
  • Upload — Local filesystem + S3 storage, single/multiple file handlers, MIME type filtering
  • WebSocket — Hub pattern, rooms, broadcast, send-to-client, ping/pong keepalive
  • i18n — JSON translation files, nested key support, locale detection (URL/cookie/header/query)
  • Mail — SMTP with TLS, HTML templates, attachments, CC/BCC
  • Event Bus — Sync/async event emission, multiple handlers per event
  • Rate Limit — Per-endpoint rate limiting with custom key functions

Infrastructure

  • Docker — Multi-stage Dockerfile, docker-compose (app + PostgreSQL + Redis)
  • Graceful shutdown — Database connection cleanup on SIGTERM

Framework Integration

  • 7 new config sections in nguyen.config.yml
  • 7 new functional options: WithDatabase, WithAuth, WithCSRF, WithUpload, WithWebSocket, WithI18n, WithMail
  • Accessor methods: app.DB(), app.Auth(), app.CSRF(), app.Uploader(), app.WSHub(), app.I18n(), app.Mailer(), app.Events()
  • Response helpers: ValidationError, Conflict, TooManyRequests

Dependencies Added

  • golang.org/x/crypto — bcrypt password hashing
  • github.com/gofiber/websocket/v2 — WebSocket support

Documentation

  • docs/backend-modules.md — Full usage guide for all new modules

Full Changelog: v1.0.6...v1.0.7

v1.0.6

13 May 23:11

Choose a tag to compare

V1.0.6

Highlights

This release consolidates all improvements since v1.0.1 into a single, clean version with full documentation and synchronized pkg.go.dev publishing.


Breaking Changes

  • File extension renamed: .nguyen.gox
    All component files now use the .gox extension — shorter, cleaner, easier to type. The .nguyen/ output directory and framework name remain unchanged.

New Features

SPA Navigation Engine (rewritten)

  • DOM morphing instead of innerHTML — preserves focus, scroll position, form state
  • Binary frame protocol for minimal network payload
  • Batch prefetch endpoint (POST /_nguyen/prefetch) — Go renders multiple routes concurrently via goroutines
  • AbortController cancels stale in-flight navigations
  • requestIdleCallback scheduling for zero main-thread blocking
  • View Transitions API support
  • In-memory LRU partial cache (128 entries, 60s TTL)

Data Loaders

  • Concurrent server-side data fetching via LoaderRegistry
  • Multiple loaders execute in parallel using goroutines
  • Context timeout and panic recovery built-in

Error Boundaries

  • Graceful render failure handling with customizable fallback HTML
  • Prevents a single broken page from crashing the server

Template Directives

  • <nguyen-fragment> — render children without wrapper div
  • <nguyen-show when="key"> / <nguyen-hide when="key"> — conditional rendering evaluated against SSR state

Middleware System

  • Named middleware registry with route guard resolution
  • Built-in CORS and RequestID middleware
  • Guards declared in .gox frontmatter: guard = "auth"

Response Helpers

  • OK, Created, NoContent, BadRequest, Unauthorized, Forbidden, NotFound, ServerError
  • Redirect, RedirectPermanent
  • Paginated with full metadata (page, total, has_next, has_prev)

Environment Injection

  • Safe access to NGUYEN_ prefixed env vars in SSR context
  • IsProduction() / IsDevelopment() helpers

Health Check

  • Built-in /_nguyen/health endpoint
  • Reports: status, version, uptime, Go version, goroutine count, memory stats

Improvements

  • Centralized version string (internal/version/version.go)
  • Dev server rate limiter increased to 600 req/min (HMR-friendly)
  • Pre-compiled regex in scoped CSS processing
  • strconv.Itoa replaces hand-rolled implementation
  • Tailwind CLI uses latest release URL

Documentation

  • Full README.md rewrite with architecture diagram
  • All 15 docs pages updated to .gox extension
  • Example templates updated

Retracted Versions

The following versions are retracted in go.mod due to incomplete publishing:

  • v1.0.2, v1.0.3, v1.0.4

Use v1.0.6 (or v1.0.1 / v1.0.5) instead.

Migration from v1.0.1

# Rename all component files
# Linux/macOS:
find pages/ app/ components/ -name "*.nguyen" -exec sh -c 'mv "$1" "${1%.nguyen}.gox"' _ {} \;

# Windows PowerShell:
Get-ChildItem -Recurse -Filter "*.nguyen" | Rename-Item -NewName { $_.Name -replace '\.nguyen$', '.gox' }

# Update Tailwind config content paths
# "*.nguyen" → "*.gox"

v1.0.1

13 May 19:40

Choose a tag to compare

Nguyen.go V1.0.1

Improvements

  • Centralized version management — version string now lives in internal/version/version.go, eliminating duplicate hardcoded values across root.go and config.go
  • Dev server rate limiter — increased from 100 to 600 req/min/IP in dev mode to prevent false blocks during HMR rapid reloads
  • Performance: scoped CSS — pre-compiled regex patterns instead of compiling inside loops on every call
  • Stdlib usage — replaced hand-rolled Itoa implementation with strconv.Itoa
  • Tailwind CLI download — uses latest release URL instead of hardcoded version, ensuring new projects always get the most recent Tailwind build

Fixes

  • Use @latest instead of hardcoded version in install commands
  • Correct clone URL (removed duplicate .go)

Full Changelog

36ebc0b...761328f

Nguyen.go v1.0.0

12 May 12:31

Choose a tag to compare

Nguyen.go v1.0.0 — Initial Release

The Go full-stack framework — file-based routing, islands architecture, and React-style hooks compiled to WebAssembly. One binary to deploy.

Highlights

  • File-system routing — drop a .nguyen file in pages/, get a route
  • React-style hooksUseState, UseEffect, UseMemo, UseCallback, UseRef, UseContext
  • Hybrid rendering — SSR, ISR, CSR per page + streaming SSR
  • Islands architecture — partial hydration with client:load, client:idle, client:visible
  • Per-route WASM code splitting — sub-100KB chunks via TinyGo
  • Single binary deploygo build → one executable, no Node.js, no Docker required
  • Built-in image optimization — WebP/AVIF conversion at /_nguyen/image
  • PWA support — auto-generated manifest.json + service worker
  • GEO (SEO for AI) — sitemap.xml, robots.txt, llms.txt, JSON-LD structured data
  • HMR — SSE-based hot module replacement in dev mode
  • Scoped CSS — component-level styles via <style scoped>
  • Tailwind CSS — standalone CLI integration (no npm)

Install

go install github.com/dev2k6/Nguyen.go/cmd/nguyen@latest