Golang中React服务端渲染(SSR)的实现与优化

Golang中React服务端渲染(SSR)的实现与优化 你好,

我正在尝试用 React 制作一个 SSR 应用,但不知道从哪里开始。我以前用 Node(ExpressJS)做过,将我的 React 应用“渲染成字符串”,然后将输出“放置”到我的 HTML 中。

有人做过类似的项目吗?或者知道如何在 Go 中将 React 应用“渲染”成字符串吗?

谢谢!

1 回复

更多关于Golang中React服务端渲染(SSR)的实现与优化的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中实现React服务端渲染(SSR)主要有两种方案:

方案一:使用Go执行Node.js(推荐)

这是最实用的方法,通过Go调用Node.js的V8引擎来执行React渲染:

package main

import (
    "context"
    "fmt"
    "os"
    "os/exec"
    "time"
)

type SSRResult struct {
    HTML string
    Error error
}

func renderReactWithNode(componentPath, props string) (string, error) {
    // 创建Node.js渲染脚本
    renderScript := fmt.Sprintf(`
        const React = require('react');
        const ReactDOMServer = require('react-dom/server');
        const App = require('%s').default;
        
        const props = %s;
        const element = React.createElement(App, props);
        const html = ReactDOMServer.renderToString(element);
        
        process.stdout.write(html);
    `, componentPath, props)

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    cmd := exec.CommandContext(ctx, "node", "-e", renderScript)
    cmd.Env = append(os.Environ(), "NODE_ENV=production")
    
    output, err := cmd.Output()
    if err != nil {
        return "", fmt.Errorf("Node渲染失败: %v", err)
    }

    return string(output), nil
}

func main() {
    props := `{"title": "Go SSR示例", "count": 42}`
    html, err := renderReactWithNode("./components/App.jsx", props)
    if err != nil {
        panic(err)
    }
    
    // 嵌入到HTML模板
    fullHTML := fmt.Sprintf(`
        <!DOCTYPE html>
        <html>
            <head>
                <title>SSR应用</title>
            </head>
            <body>
                <div id="root">%s</div>
                <script>
                    window.__INITIAL_STATE__ = %s;
                </script>
                <script src="/static/bundle.js"></script>
            </body>
        </html>
    `, html, props)
    
    fmt.Println(fullHTML)
}

方案二:使用Go的JavaScript运行时

使用支持React的Go JavaScript运行时,如gojav8go

package main

import (
    "fmt"
    "github.com/dop251/goja"
    "io/ioutil"
)

func renderReactWithGoja() (string, error) {
    // 读取React和ReactDOM库
    reactSource, _ := ioutil.ReadFile("./node_modules/react/umd/react.production.min.js")
    reactDOMSource, _ := ioutil.ReadFile("./node_modules/react-dom/umd/react-dom-server.browser.production.min.js")
    
    vm := goja.New()
    
    // 注入React和ReactDOM
    _, err := vm.RunString(string(reactSource))
    if err != nil {
        return "", err
    }
    
    _, err = vm.RunString(string(reactDOMSource))
    if err != nil {
        return "", err
    }
    
    // 定义React组件
    componentCode := `
        const MyApp = ({ title }) => React.createElement('h1', null, title);
        const element = React.createElement(MyApp, { title: 'Go SSR' });
        ReactDOMServer.renderToString(element);
    `
    
    result, err := vm.RunString(componentCode)
    if err != nil {
        return "", err
    }
    
    return result.String(), nil
}

优化方案:预渲染缓存

package main

import (
    "sync"
    "time"
)

type SSRCache struct {
    mu    sync.RWMutex
    cache map[string]*CacheEntry
}

type CacheEntry struct {
    HTML      string
    Timestamp time.Time
    ExpiresAt time.Time
}

func NewSSRCache(ttl time.Duration) *SSRCache {
    return &SSRCache{
        cache: make(map[string]*CacheEntry),
    }
}

func (c *SSRCache) Get(key string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    
    entry, exists := c.cache[key]
    if !exists || time.Now().After(entry.ExpiresAt) {
        return "", false
    }
    
    return entry.HTML, true
}

func (c *SSRCache) Set(key, html string, ttl time.Duration) {
    c.mu.Lock()
    defer c.mu.Unlock()
    
    c.cache[key] = &CacheEntry{
        HTML:      html,
        Timestamp: time.Now(),
        ExpiresAt: time.Now().Add(ttl),
    }
}

// 使用示例
func renderWithCache(cache *SSRCache, component, props string) (string, error) {
    cacheKey := fmt.Sprintf("%s:%s", component, props)
    
    if html, found := cache.Get(cacheKey); found {
        return html, nil
    }
    
    html, err := renderReactWithNode(component, props)
    if err != nil {
        return "", err
    }
    
    cache.Set(cacheKey, html, 5*time.Minute)
    return html, nil
}

完整HTTP服务器示例

package main

import (
    "encoding/json"
    "net/http"
    "time"
)

func main() {
    cache := NewSSRCache(5 * time.Minute)
    
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // 从请求中提取props
        props := map[string]interface{}{
            "path": r.URL.Path,
            "userAgent": r.UserAgent(),
        }
        
        propsJSON, _ := json.Marshal(props)
        
        // 渲染React组件
        html, err := renderWithCache(cache, "./components/App.jsx", string(propsJSON))
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        
        // 发送完整HTML响应
        w.Header().Set("Content-Type", "text/html; charset=utf-8")
        w.Write([]byte(html))
    })
    
    http.ListenAndServe(":8080", nil)
}

这些示例展示了在Go中实现React SSR的核心方法。第一种方案通过调用Node.js是最稳定和兼容性最好的,第二种方案使用Go JavaScript运行时可以减少外部依赖,但需要处理React库的兼容性问题。

回到顶部