# Packages
# README
Golang SSR for Vite Apps Example
Minimal example of using V8 to render a Frontend App built with Vite.
Used Packages
- v8go - Go bindings for V8 JavaScript engine
- Vitesse Lite - Frontend App Template written in Vue and built with Vite
Covered features
- Fully functional frontend application (not just a simple "Hello World")
- SSR for the frontend application
- V8 Isolate pool to avoid creating a new Isolate for each request
- Embedded frontend application in the binary to reduce the file system calls
How to run the example
docker compose up -d
curl http://localhost:8080/hi/test
Note: if there is some issues with building the image, remove --platform=linux/amd64
options from Dockerfile. This was used to avoid issues when running on Apple M1 architecture.
Pros and Cons
Pros:
- No need to use Node.js
- All in one binary
Cons:
- The more big js-bundle size the slower rendering
- You can't use Vite features like hot module replacement
- To build the server you need a lot of c-libs installed
Keynotes
Separate config for Vite Build
The default config is configured in vite.config.ts
and the additional configuration for the SSR build is located in vite.config.prod.ts
.
SSR config builds the frontend application with target cjs
as it is required for the V8 engine.
Entry point for SSR and Client are different
See:
- Client-side entry:
client/src/entry-client.ts
- Server-side entry:
client/src/entry-server.ts
How to speed up SSR more
Split frontend build for server for multiple files (currently it's a single file).
A require
function must be implemented in the V8 context to load the files.
It's better to cache the required files in memory.
Hot Reloading
It's not possible to use hot reloading with V8. For frontend development it's better to use Vite directly and store code it in another repo. For backend development use any watcher to rebuild all (e.g. air).
Build without Vite and with Go only
Some cool features of Vite will be missing (e.g. Glob Import, Dynamic Import, hot module replacement, etc.), but it's possible to build the frontend application with ESBuild - a Go-based bundler. It has a Go API and it's very fast. Actually, it's used by Vite under the hood.
Approaches to render the frontend application
First Approach
Fastest one. The idea is to run SSR script, get function with sensitive args and run it.
Go Code
iso
object has Isolate *v8go.Isolate
and RenderScript *v8go.UnboundScript
(it was compiled before).
// renderer.go
// ...
func (r *Renderer) Render(urlPath string) (string, error) {
iso := r.pool.Get()
defer r.pool.Put(iso)
ctx := v8go.NewContext(iso.Isolate)
defer ctx.Close()
iso.RenderScript.Run(ctx)
renderCmd := fmt.Sprintf(`ssrRender("%s")`, urlPath)
val, err := ctx.RunScript(renderCmd, r.ssrScriptName)
if err != nil {
if jsErr, ok := err.(*v8go.JSError); ok {
err = fmt.Errorf("%v", jsErr.StackTrace)
}
return "", nil
}
return val.String(), nil
}
JS Code
// entry-server.ts
// ...
function ssrRender(url: string) {
return render(url).then((html) => {
return html
})
}
(globalThis as any).ssrRender = ssrRender
Second Approach
Create global object in Go with render
function that recieves rendered html, concat string in Go and return.
Go Code
iso
object has Isolate *v8go.Isolate
and RenderScript *v8go.UnboundScript
(it was compiled before).
// renderer.go
// ...
func (r *Renderer) Render(urlPath string) (string, error) {
iso := r.pool.Get()
defer r.pool.Put(iso)
outputHTML := ""
ssrObject := v8go.NewObjectTemplate(iso.Isolate)
ssrObject.Set("href", urlPath)
ssrObject.Set("render", v8go.NewFunctionTemplate(iso.Isolate, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
args := info.Args()
if len(args) > 0 {
outputHTML = args[0].String()
}
return nil
}))
globalObject := v8go.NewObjectTemplate(iso.Isolate)
globalObject.Set("ssr", ssrObject)
ctx := v8go.NewContext(iso.Isolate, globalObject)
defer ctx.Close()
start := time.Now()
iso.RenderScript.Run(ctx)
//if _, err := ctx.RunScript(r.scriptSource, r.Path); err != nil {
// return "", err
//}
fmt.Println("Script run:", time.Since(start))
return outputHTML, nil
}
JS Code
// entry-server.ts
// ...
if (typeof ssr !== 'undefined') {
render(ssr.href).then((html) => {
ssr.render(html)
})
}