Quick Start
Walk through creating and running your first rfw v2 application. Assumes your environment meets the requirements.
Install the CLI
go install github.com/rfwlab/rfw/cmd/rfw@latest
rfw --version
Scaffold a Project
rfw init github.com/username/hello-rfw
cd hello-rfw
Generated structure:
hello-rfw/
main.go
components/
host/
rfw.json
go.mod
components/, client-side components and.rtmltemplates
host/, server-side host components (SSC)
rfw.json, build config (SSC enabled by default)
Use --skip-tidy to skip go mod tidy during init.
A First Component
In v2, composition.New(&struct{}) auto-wires everything based on field types — no struct tags required.
Counter.rtml
Templates are discovered by convention: the struct name maps to StructName.rtml. Place the template alongside your Go file or in any registered embed.FS.
<root>
<button @on:click:Increment>Count: {@expr:count}</button>
</root>
@on:click:Incrementfires theIncrementmethod on the component
@expr:countreads thecountsignal reactively
Counter.go
//go:build js && wasm
package main
import (
"embed"
"github.com/rfwlab/rfw/v2/composition"
"github.com/rfwlab/rfw/v2/router"
"github.com/rfwlab/rfw/v2/types"
)
//go:embed Counter.rtml
var fs embed.FS
func init() {
composition.RegisterFS(&fs)
}
type Counter struct {
composition.Component
Count types.Int
}
func (c *Counter) Increment() {
c.Count.Set(c.Count.Get() + 1)
}
func (c *Counter) OnMount() {
c.Count.Set(0)
}
func main() {
router.Page("/", func() *types.View {
view, err := composition.New(&Counter{})
if err != nil {
log.Fatal(err)
}
return view
})
router.InitRouter()
select {}
}
What happened
| Field/Method | Effect |
|---|---|
Count types.Int |
Signal type detected → auto-wired as reactive prop |
Increment() method |
Auto-registered as DOM event handler |
OnMount() |
Auto-discovered lifecycle hook, called after DOM insertion |
Counter.rtml |
Auto-found by struct name convention via composition.RegisterFS |
composition.Component embed |
Provides Store(), History(), and other composition helpers |
No tags. Field types determine everything. Methods become handlers. Templates found by convention.
Optional: Template() string Method
Override the template by defining a Template() string method:
func (c *Counter) Template() string {
return "<root><button @on:click:Increment>Count: @signal:Count</button></root>"
}
Run the Development Server
rfw dev --debug
- Compiles Go to
app.wasm(served as Brotli-compressedapp.wasm.br)
- Serves static files under
/
- Rebuilds and reloads on file changes
- Builds and runs host binary from
host/when present
Flags:
--port, set port (default 8080)
--host, expose to network
--debug, enable logs and profiling endpoints
Environment variables:
RFW_PORT, set port
RFW_LOG_LEVEL, set log level (debug,info,warn,error)
Build for Production
rfw build
Outputs:
build/client/, Wasm bundle and assets
build/static/, copied static files
build/host/, host binary for SSC
Production builds use -trimpath and -ldflags="-s -w" to strip debug info. Export RFW_SKIP_STRIP=1 to keep symbols. During development, rfw dev sets RFW_DEV_BUILD=1, enabling the rfwdev build tag.
What You Learned
- Installing the CLI and scaffolding a project
- Creating a component with type-based composition
- Templates found by convention (
StructName.rtml) orTemplate()method
- Registering routes with
router.Page()
- Running the dev server and building for production