Wood Chen

Implementing URL Rewrite Rules for SPAs (Nextjs, react, etc.) in Go

0 comments109 views57 words

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 前后端分离+nginx can 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

Related posts

Comments 0