A full-stack Go web framework for building modern web applications. Nguyen.go compiles Go to WebAssembly (via TinyGo) for the frontend, while the backend runs natively on Go using Fiber. Think Next.js for Go — with server-side rendering (SSR), incremental static regeneration (ISR), islands architecture, and a React-style virtual DOM reconciler.
- Go + TinyGo WASM — Write frontend components in Go, compile to WebAssembly
- React-style reconciler — Virtual DOM diffing with keyed reconciliation
- Template syntax —
.nguyenfiles with Go frontmatter and HTML templates - SSR / ISR / CSR — Server-side rendering, Incremental Static Regeneration, or Client-Side Rendering per page
- Streaming SSR — Transfer-Encoding: chunked responses for faster Time to First Byte
- Hot Module Replacement — SSE-based HMR for CSS and full page reloads
- Control flow directives —
<ng-if>,<ng-else-if>,<ng-else>,<ng-for> - Named slots — Multiple slot regions in layouts
- Scoped CSS — Component-scoped styles via
<style scoped> - Variable interpolation —
{stateVar}in templates with reactive hydration
- File-system routing —
pages/index.nguyen→/,pages/about.nguyen→/about - Nested layouts — Auto-discovered
layout.nguyenper directory hierarchy - Dynamic routes —
pages/blog/[slug].nguyen→/blog/:slug - Catch-all routes —
pages/[...path].nguyen→ wildcard matching - Optional params —
pages/[[...path]].nguyen→ optional catch-all - Route guards — Named guard registration with
RegisterGuard - Search params —
UseSearchParams()with reactive updates
UsePageData— Server-injected props via SSRUseQuery/UseMutation— Async data fetching with cachingUseForm— Form state management with per-field validatorsFetchJSON— HTTP client helper
UseState— Reactive state hookUseEffect/UseLayoutEffect— Side effect hooksUseMemo/UseCallback— Performance optimization hooksUseLocalStorage/UseSessionStorage— Cross-tab synced browser storageUseGlobalState— App-level shared state
Suspense— Async component boundary with fallbackErrorBoundary— Panic recovery in child componentsNguyenTransition— CSS enter/leave animationsMemo— Shallow props comparison for memoizationNguyenLink— Active path-aware link component with SPA navigation
- Per-route code splitting — Separate
.wasmchunks per page + shared core - Islands architecture — Partial hydration of interactive components
- PWA support — Auto-generated
manifest.json+sw.jswith offline caching - Image optimization — Built-in
/_nguyen/imageendpoint with WebP/AVIF conversion - Bundle analyzer — HTML report of WASM chunk sizes
- GEO / SEO — Sitemap, robots.txt, llms.txt, Open Graph, JSON-LD, FAQ schema, breadcrumbs
- Go 1.25+
- TinyGo (optional, for WASM compilation — SSR works without it)
go install github.com/dev2k6/Nguyen.go/cmd/nguyen@latestOr build from source:
git clone https://github.com/dev2k6/Nguyen.go.git
cd nguyen.go
go build -o nguyen ./cmd/nguyen/# Create a new project
nguyen create my-app
# With Tailwind CSS (standalone CLI, no npm)
nguyen create my-app --tailwind
# Enter the project
cd my-app
# Start the dev server with HMR
nguyen dev
# Open http://localhost:3000| Command | Description |
|---|---|
nguyen create <name> |
Scaffold a new project from template |
nguyen dev |
Start dev server with HMR and file watching |
nguyen build |
Compile .nguyen files to optimized WASM + static HTML |
nguyen start |
Start production server from build output |
nguyen export |
Pre-render all pages to static HTML for CDN deployment |
nguyen check |
Validate .nguyen file syntax without building |
nguyen dev --port 8080 # Custom port
nguyen dev --pages src/pages # Custom pages directory
nguyen dev --config my.config.yml # Custom config file
nguyen build --per-route # Code-split per route (default)
nguyen build --no-wasm # SSR-only build (skip TinyGo)
nguyen build --analyze # Generate bundle size report
nguyen export --output dist # Custom output directory
nguyen start --port 8080 # Custom port
nguyen start --dir .nguyen # Build directory to servemy-app/
├── pages/ # File-system routes (.nguyen files)
│ ├── index.nguyen # → /
│ ├── about.nguyen # → /about
│ ├── 404.nguyen # → catch-all fallback
│ ├── layout.nguyen # Root layout (wraps all pages)
│ └── blog/
│ ├── index.nguyen # → /blog
│ ├── [slug].nguyen # → /blog/:slug
│ └── layout.nguyen # Blog-specific layout
├── api/ # API route handlers (Go)
│ └── routes.go
├── styles/ # CSS files
│ ├── input.css # Tailwind input
│ └── output.css # Compiled CSS
├── public/ # Static assets (served at /public/*)
├── config/
│ └── nguyen.config.yml # Framework configuration
├── go.mod
└── go.sum
A .nguyen file has two sections separated by ---:
- Frontmatter — Go code (imports, state, handlers, metadata)
- Template — HTML with reactive interpolation
---
import "nguyen.go/pkg/core"
count, setCount := core.UseState("count", 0)
func increment() {
setCount(count + 1)
}
func decrement() {
setCount(count - 1)
}
export const metadata = {
title: "Counter Example",
description: "A simple counter built with Nguyen.go",
}
---
<section class="counter">
<h1>Count: {count}</h1>
<button @click="increment()">+</button>
<button @click="decrement()">-</button>
</section>
<style scoped>
.counter {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
</style>
<button @click="handler()">Click me</button>
<input @input="onInput()" />
<form @submit="onSubmit()">...</form>Supported events: @click, @input, @change, @submit, @keydown, @keyup, @focus, @blur, @mouseenter, @mouseleave, @dblclick
<ng-if condition="isLoggedIn">
<p>Welcome back!</p>
</ng-if>
<ng-else>
<p>Please log in.</p>
</ng-else>
<ng-for items="posts" item="post" index="i">
<article>
<h2>{post.Title}</h2>
<p>{post.Body}</p>
</article>
</ng-for>Create a layout.nguyen in any directory. It wraps all pages in that directory and below:
---
export const metadata = {
title: "My App",
}
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/styles/output.css">
<nguyen-head />
</head>
<body>
<nav>...</nav>
<main>
<nguyen-slot />
</main>
<footer>...</footer>
</body>
</html>
Use <nguyen-slot /> for the default content slot and <nguyen-slot name="sidebar" /> for named slots.
Create config/nguyen.config.yml:
name: my-app
version: 1.0.0
server:
host: localhost
port: 3000
strict_routing: false
case_sensitive: false
render:
mode: ssr # csr | ssr | isr
stream: true # enable streaming SSR
extract_critical_css: true
wasm:
entry: app/main.go
output: .nguyen/app.wasm
target: wasm
optimize: true
hmr:
enabled: true
sse: /_nguyen/hmr
watch:
- pages/
- components/
- styles/
images:
formats: [webp, avif]
sizes: [640, 750, 1080, 1920]
quality: 80
cache_ttl: 86400
head:
title_template: "%s — My App"
default_title: "My App"
meta:
description: "Built with Nguyen.go"
preload_wasm: true
geo:
enabled: true
site_name: "My App"
site_url: "https://example.com"
sitemap:
enabled: true
default_changefreq: weekly
default_priority: 0.7
robots:
enabled: true
allow_ai_bots: false
llms_txt:
enabled: true
max_chars_per_page: 5000
pwa:
enabled: true
short_name: "MyApp"
description: "My Progressive Web App"
theme_color: "#6366f1"
background_color: "#ffffff"
display: standalone
icons:
- src: /icon-192.png
sizes: 192x192
type: image/png
- src: /icon-512.png
sizes: 512x512
type: image/pngPages are rendered on the server for every request. Best for dynamic content and SEO.
render:
mode: ssr
stream: true # optional: stream HTML chunksPages are cached after first render and revalidated in the background. Set per-page revalidation time:
---
export const revalidate = 60 // revalidate every 60 seconds
---
<h1>This page is cached with ISR</h1>
Templates are sent as-is; WASM handles all rendering on the client. Best for highly interactive apps.
render:
mode: csrNguyen.go includes built-in support for AI-friendly SEO:
---
export const geo = {
pageType: "Article",
datePublished: "2026-01-01T00:00:00Z",
dateModified: "2026-05-10T00:00:00Z",
author: "Your Name",
speakable: [".main-content"],
breadcrumbs: [
{name: "Home", url: "/"},
{name: "Blog", url: "/blog"},
{name: "This Post", url: "/blog/this-post"},
],
faqs: [
{question: "What is Nguyen.go?", answer: "A full-stack Go web framework."},
],
tags: ["go", "webassembly", "framework"],
}
---
Auto-generated endpoints:
/sitemap.xml— XML sitemap with all routes/robots.txt— Crawler directives/llms.txt— LLM-friendly site summary/.well-known/ai-bot.txt— AI bot permissions
Use Nguyen.go as a library in your own Go application:
package main
import (
"nguyen.go/pkg/nguyen"
"github.com/gofiber/fiber/v2"
)
func main() {
app := nguyen.New(
nguyen.WithPort(3000),
nguyen.WithPages("pages"),
nguyen.WithPublic("public"),
nguyen.WithStyles("styles"),
nguyen.WithSetup(func(f *fiber.App) {
// Add custom Fiber routes/middleware
f.Get("/api/health", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"status": "ok"})
})
}),
)
app.Listen(":3000")
}nguyen build
go build -o server ./cmd/nguyen/
./server start --dir .nguyen --port 8080nguyen export --output dist
# Upload dist/ to any static host (Netlify, Vercel, Cloudflare Pages, S3)FROM golang:1.25-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o nguyen ./cmd/nguyen/
RUN ./nguyen build --no-wasm
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/nguyen .
COPY --from=builder /app/.nguyen .nguyen
COPY --from=builder /app/config config
EXPOSE 3000
CMD ["./nguyen", "start"]┌─────────────────────────────────────────────────────────┐
│ CLI (cobra) │
│ create │ dev │ build │ start │ export │ check │
└────────────────────────┬────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────┐
│ Internal Packages │
├──────────┬──────────┬──────────┬──────────┬─────────────┤
│ parser │ router │ render │ compiler │ server │
│ (.nguyen│ (file- │ (SSR, │ (TinyGo │ (Fiber, │
│ lexer) │ system) │ stream) │ WASM) │ HMR) │
├──────────┼──────────┼──────────┼──────────┼─────────────┤
│ config │ cache │ geo │ pwa │ optimizer │
│ (YAML) │ (ISR) │ (SEO) │(manifest)│ (images) │
└──────────┴──────────┴──────────┴──────────┴─────────────┘
│
┌────────────────────────▼────────────────────────────────┐
│ Public Packages │
├─────────────────┬───────────────────────────────────────┤
│ pkg/nguyen │ pkg/core │
│ (App, Options) │ (VNode, Hooks, Reconciler, Router) │
└─────────────────┴───────────────────────────────────────┘
Copy ftdetect/nguyen.vim to your config:
# Neovim
cp ftdetect/nguyen.vim ~/.config/nvim/ftdetect/
# Vim
cp ftdetect/nguyen.vim ~/.vim/ftdetect/This enables Go syntax highlighting for .nguyen files.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Run tests (
go test ./...) - Run syntax check on examples (
go run ./cmd/nguyen check --pages examples/hello-nguyen/pages) - Commit your changes
- Push to the branch
- Open a Pull Request
MIT
Thái Nguyên thainguyen.junior@gmail.com