Author server and runtime components in Go, importing arbitrary packages.
#Go Components
Server and runtime components can be written in Go instead of TSX. A Go component is a plain function that receives props and returns markup (or a string/number). It compiles into the same Go host binary as the Go API routes and can import any Go package.
#Quick start
Go is enabled by default and looks in src/components and src/api, so if that layout suits you there is no config to write. To use it:
- Install the Go toolchain (1.21+). Go components are compiled with
go buildat build time; there is no prebuilt path.
- (Optional) configure
krate.config.tsonly to customize paths or add modules:
import { defineConfig } from "@krate/core";
export default defineConfig({
go: {
componentDirs: ["src/components"], // where *.go components live
// apiDirs: ["src/api"], // where Go API routes live
// deps: [{ path: "github.com/google/uuid", version: "v1.6.0" }],
// replaces: [{ from: "example.com/local", to: "../local" }],
},
});See Configuration for every key.
- Set up editor intellisense (one command). This writes a
go.mod+go.work, vendors the helper module under.krate/goext, and adds.krate/typesto yourtsconfig.jsoninclude:
krate go:init If you skip this you can still build, you just lose goimports/gopls completion for the helper import.
- Add a component under
src/components(see below), import it from TSX, and runkrate dev.
#Authoring
A component file lives under a go.componentDirs directory (default src/components) and exports a function whose tier is set by the file suffix or a directive:
// src/components/badge.server.go
package components
import kr "github.com/kratejs/krate/packages/compiler/goext/krate"
type BadgeProps struct {
Label string `json:"label"`
Items []string `json:"items"`
Count int `json:"count"`
Kids kr.HTML `json:"children"` // JSX children, pre-rendered to HTML
}
// krate:component server
func Badge(p BadgeProps) kr.Node {
return kr.Span(
kr.Class("badge"),
kr.Text(p.Label),
kr.Each(p.Items, func(it string) kr.Node { return kr.Li(nil, kr.Text(it)) }),
)
}- Tier:
.server.gorenders at build time (static HTML, zero runtime artifact)..runtime.gorenders per request as a region. A//krate:component server|runtimedirective overrides the suffix. A file with neither is a plain package file other components can import. - Props: one struct (or pointer) argument. Fields decode from the JSX props by their
jsontag. Numbers, booleans, arrays, objects, and pointers map to the matching Go types. - Return:
kr.Nodeorkr.HTML(markup),string/number/bool(rendered as text), or(T, error). - Children: the JSX children of
<Badge>…</Badge>are rendered to HTML and passed under the struct field taggedjson:"children"(typedkr.HTML). - Escaping: the builder escapes text and attribute values by default;
kr.HTML/kr.Rawinsert markup verbatim.
Files in the same directory form one Go package, so a component may call helpers in a sibling file.
#Building markup
goext/krate is a small, stdlib-only builder. Text and attribute values are escaped for you, and markup is composed as data rather than concatenated:
func Card(p CardProps) kr.Node {
return kr.Article(kr.Class("card", p.Tone),
kr.H2(nil, kr.Text(p.Title)),
kr.P(nil, kr.Text(p.Body)),
kr.Ul(nil, kr.Each(p.Tags, func(t string) kr.Node {
return kr.Li(kr.Attrs{"data-tag": t}, kr.Text(t))
})),
)
}- Elements:
Div,Span,P,A,Ul/Ol/Li,H1-H6,Section,Article,Form,Button,Input,Img,Code,Pre, and more, pluskr.El(tag, attrs, children...)for any other tag. Void elements (Img,Input,Br,Hr,Link,Meta,Source) take attributes only. - Attributes:
kr.Attrs{"href": "/x", "hidden": true, "class": []string{"a"}, "style": map[string]string{"color": "red"}}. Values may bestring,bool(bare attribute),[]string(space-joined), ormap[string]string(k:v;...). Shorthands:kr.Class("a", "b")andkr.Style("color", "red"). - Text vs markup:
kr.Text(s)escapes,kr.Raw(s)and thekr.HTMLtype insert trusted markup verbatim, andkr.URL(s)escapes a URL path. - Grouping:
kr.Frag(nodes...),kr.Join(sep, parts...), andkr.Each(items, fn)(generics) build lists with no wrapper element. - Return type: a component may return
kr.Nodeorkr.HTML(both render to markup), and a Go component can call another Go component directly.
#Templates
For markup-heavy components you can use html/template with context-aware auto-escaping, alone or alongside the builder:
var card = kr.MustTemplate(`<article class="card"><h2>{{.Title}}</h2></article>`)
func Card(p CardProps) (kr.HTML, error) {
return card.Render(p)
}kr.SafeHTML(node) marks a value as trusted inside a template, and kr.NewTemplate(name, src) returns a parse error instead of panicking.
#Importing from TSX
import { Badge } from "@/components/badge.server.go";
export default function Page() {
return <Badge label="Hi" items={["a", "b"]} count={2}>child</Badge>;
}The compiler resolves the .go import, serializes the props to JSON, and either inlines the HTML (server tier) or emits a request-time region (runtime tier). Every .go import is a normal ES import — the @/ alias and relative paths both work.
#Runtime regions
*.runtime.go components run in the Go host per request. A page that imports one is force-rendered as a streaming page: the static shell carries a region marker and krate serve renders it via the host (over AF_UNIX) at request time, splicing the HTML in - exactly like TSX runtime components, but on the Go host. A runtime component may take a second kr.Request parameter to read the incoming method, URL, route params, query, and headers:
func Show(p ShowProps, r kr.Request) kr.HTML {
return kr.HTML("<h1>" + kr.Escape(r.Param("id")) + "</h1>")
}Add a // krate:revalidate 60 comment to cache the region for 60 seconds (the Go equivalent of export const revalidate = 60).
#TypeScript types
Krate generates ambient module declarations under .krate/types/ so the .go imports type-check in your editor. Add .krate/types to your tsconfig.json include (the create-krate-app scaffold and krate go:init both do this):
{ "include": ["src", ".krate/types"] }Prop types come from the Go struct:
| Go | TypeScript |
|---|---|
string | string |
int, int64, float64 | number |
bool | boolean |
[]T | T[] |
map[string]T | Record<string, T> |
struct (by json tag) | interface |
kr.HTML, kr.Node | JSX.Element |
krate types (or any build) regenerates them. A component type declared in a sibling file in the same package resolves too.
#Intellisense
The helper packages live at real import paths (github.com/kratejs/krate/packages/compiler/goext/krate and .../goext/runtime) so goimports/gopls provide completion and type information. krate go:init writes a project go.mod + go.work and vendors the helper module under .krate/goext so those imports resolve offline too. krate doctor reports whether the setup is complete.
Note: files named with a route param (e.g.
[id].go) are not valid Go filenames, so they cannot get editor intellisense even though Krate compiles them (it copies them to a sanitized name first).
#Troubleshooting
- Import is red / no completion. Run
krate go:init, then reload your editor. Confirmtsconfig.jsonincludelists.krate/typesand a rootgo.modexists. - "Go routes/components require the Go toolchain." Install Go and make sure
gois onPATH. - Prop is
unknown/ wrong type. The props type must live in the component file or a sibling file of the same package; types from other packages map tounknown. - Editor sees
[id].goas invalid. Route-param filenames can't be Go files; rename is not possible without changing the route. Everything else still compiles.
#Configuration
See Config Reference for the go block (enabled, module, deps, replaces, exclude, tidy, componentDirs, apiDirs, apiEnabled). Go components require the Go toolchain at build time.
Last updated: 2026-10-16
Contributors: Noinkin