This guide shows how to set up a Go application that serves a React (TypeScript) frontend, all bundled into a single binary, while keeping the development environment clean and reproducible using a Docker Dev Container in GoLand.
By the end, you'll have:
- A Go project with a Dev Container configuration
- A React + TypeScript app living inside the Go project
- The React build output embedded directly into the Go binary
Prerequisites
- GoLand installed with the Dev Container plugin
- Docker running
Step 1: Create a New Go Project in GoLand
Open GoLand and create a new project with a minimal main.go file:
- Go to File → New → Project
- Select Go from the left panel
- Set your project location (e.g., '~/projects/go-react-app')
- Leave GOROOT empty
- Click Create
- Create an empty main.go file in the project's root directory
Step 2: Add a Dev Container Configuration
A Dev Container lets you define your full development environment (Go, Node.js, and any other tools) inside a Docker container that GoLand connects to seamlessly. This is especially useful if you want the isolation and reproducibility benefits of Docker without having to write and maintain Dockerfiles yourself.
Rather than walk through every IDE (Integrated Development Environment) dialog (which can vary by GoLand version), follow the official JetBrains guide for starting a Dev Container from scratch. GoLand comes with predefined Dev Container templates. Select Go & Typescript from the Dev Container template list.
The Dev Container specification is an open standard originally created by Microsoft, and the devcontainer.json format is not tied to any specific IDE.
2.1: Forward the Vite Dev Server Port
When your project runs inside a Dev Container, the Vite dev server binds to a port inside the container, not on your host machine. Without port forwarding, opening http://localhost:5173 in your browser won't work, the host simply has no idea that port exists.
To fix this, edit the forwardPorts entry in your devcontainer.json:
"forwardPorts": [5173],Step 3: Create the React + TypeScript App
With your Dev Container running (and Node.js available inside it), open GoLand's integrated terminal and scaffold a new React app inside your Go project.
We'll keep the frontend in a dedicated _ui/ directory:
npm create vite@latest _ui -- --template react-tsWe prefix the folder name with an underscore because Go tooling ignores directories that start with _ or . by default.
Your project structure should now look similar to this:
go-react-app/
├── .devcontainer/
│ └── devcontainer.json
├── _ui/
│ ├── src/
│ │ ├── App.tsx
│ │ └── main.tsx
│ ├── index.html
│ ├── package.json
│ ├── tsconfig.json
│ └── vite.config.ts
├── go.mod
└── main.goForwarding a port in devcontainer.json tells the Dev Container runtime to tunnel traffic from the host to the container, but by default Vite binds only to 127.0.0.1 (localhost) inside the container. That means it only accepts connections originating from within the container itself. The port forwarder, which connects from outside, gets refused. Setting host: '0.0.0.0' tells Vite to listen on all network interfaces inside the container, so the forwarded traffic can actually reach it.
Update _ui/vite.config.ts to add a server block:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0',
port: 5173,
},
})The port field is technically optional since 5173 is already Vite's default, but it makes it explicit and no one has to guess.
Verify the dev server works:
cd _ui && npm run devYou should see the default Vite + React page at http://localhost:5173. Once confirmed, stop the dev server. In the terminal where it's running, just press Ctrl + C. We are now going to build it for production and embed it in Go.
Step 4: Build the React Distribution and Embed It in Go
This is where it all comes together. Go's embed package lets you bundle static files directly into your compiled binary, meaning you ship a single self-contained executable.
4.1: Configure the Vite Build Output
By default, Vite outputs the production build to a dist/ directory relative to where vite.config.ts is located. In our case _ui/dist/. We change the output directory to web/dist so that the built assets sit inside the Go module tree under a clean, dedicated package. This matters because Go's //go:embed directive does not allow .. in its paths, meaning the .go file containing the embed directive and the directory being embedded must share the same sub tree. By outputting directly into web/dist, our Go application can embed it with a simple //go:embed dist.
Update _ui/vite.config.ts to set the output directory:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0',
port: 5173,
},
build: {
outDir: '../web/dist',
emptyOutDir: true,
},
})Build the React app:
cd _ui && npm run buildThis generates a web/dist/ directory containing index.html, JavaScript bundles, and static assets.
4.2: Create a Go File to Embed the Assets
Create a new file web/embed.go to hold the embed directive:
package web
import (
"embed"
"io/fs"
"log"
"net/http"
)
//go:embed dist
var distFS embed.FS
func Handler() http.Handler {
dist, err := fs.Sub(distFS, "dist")
if err != nil {
log.Fatal("fs.Sub failed:", err)
}
return http.FileServer(http.FS(dist))
}4.3: Serve the Embedded Files from Go
Update main.go to serve the embedded React app over HTTP:
package main
import (
"go-react-app/web"
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.Handle("/", web.Handler())
log.Println("Listening on :8080")
err := http.ListenAndServe(":8080", mux)
if err != nil {
log.Fatal(err)
}
}4.4: Build and Run
From the project root, run:
go build -o go-react-app .
./go-react-appOpen http://localhost:8080. You should see your React app, served entirely by Go, with no separate Node.js process needed:
Conclusion
This allows to distribute tools and internal apps. One binary, zero runtime dependencies, instant startup. From here, you can extend the Go application with your business logic and build out the React UI according to your needs.