Implementing URL Rewrite Rules for SPAs (Nextjs, react, etc.) in Go
This post was translated from Chinese by AI. If anything reads oddly, the Chinese original is authoritative. 中文原文
Originally published at https://www.sunai.net/t/745
For a simple application, deploying with
前后端分离+nginxcan be a bit cumbersome. Dynamic routing, like in random-api-go, also makes proxying tricky. So a simple approach is to build the frontend and let Go serve the static files.
handlers/static_handler.go
package handlers
import (
"net/http"
"os"
"path/filepath"
"strings"
)
type StaticHandler struct {
staticDir string
}
func NewStaticHandler(staticDir string) *StaticHandler {
return &StaticHandler{
staticDir: staticDir,
}
}
// ServeStatic handles static file requests with logic similar to Nginx try_files
func (s *StaticHandler) ServeStatic(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
// Implement try_files $uri $uri/ @router logic
filePath := s.tryFiles(path)
// Set the correct Content-Type
s.setContentType(w, filePath)
// Serve the file
http.ServeFile(w, r, filePath)
}
// tryFiles implements logic similar to Nginx try_files
func (s *StaticHandler) tryFiles(requestPath string) string {
// Clean up the path
if requestPath == "" {
requestPath = "/"
}
// Remove query parameters
if idx := strings.Index(requestPath, "?"); idx != -1 {
requestPath = requestPath[:idx]
}
// 1. Try $uri - the direct file path
directPath := filepath.Join(s.staticDir, requestPath)
if s.fileExists(directPath) && !s.isDirectory(directPath) {
return directPath
}
// 2. Try $uri/ - index.html in the directory
if requestPath != "/" {
dirPath := filepath.Join(s.staticDir, requestPath)
if s.isDirectory(dirPath) {
indexPath := filepath.Join(dirPath, "index.html")
if s.fileExists(indexPath) {
return indexPath
}
}
// Also try adding the .html extension
htmlPath := directPath + ".html"
if s.fileExists(htmlPath) {
return htmlPath
}
}
// 3. @router - fall back to index.html in the root directory (SPA routing)
fallbackPath := filepath.Join(s.staticDir, "index.html")
return fallbackPath
}
// fileExists checks whether a file exists
func (s *StaticHandler) fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
// isDirectory checks whether the path is a directory
func (s *StaticHandler) isDirectory(path string) bool {
info, err := os.Stat(path)
if err != nil {
return false
}
return info.IsDir()
}
// setContentType sets the correct Content-Type
func (s *StaticHandler) setContentType(w http.ResponseWriter, filePath string) {
ext := strings.ToLower(filepath.Ext(filePath))
switch ext {
case ".html":
w.Header().Set("Content-Type", "text/html; charset=utf-8")
case ".css":
w.Header().Set("Content-Type", "text/css; charset=utf-8")
case ".js":
w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
case ".json":
w.Header().Set("Content-Type", "application/json; charset=utf-8")
case ".png":
w.Header().Set("Content-Type", "image/png")
case ".jpg", ".jpeg":
w.Header().Set("Content-Type", "image/jpeg")
case ".gif":
w.Header().Set("Content-Type", "image/gif")
case ".svg":
w.Header().Set("Content-Type", "image/svg+xml")
case ".ico":
w.Header().Set("Content-Type", "image/x-icon")
}
}
Last updated 2025-07-03
Comments 0