用只有一个文件的方式,将Next.js的web应用程序变得更加轻巧
我们所说的“轻量”是指内存使用量较少的意思。
基于 Node.js 的应用程序往往容易占用大量内存。
尤其困扰的是,这种需要启动多个应用程序才能进行操作确认的开发。而且,PC的规格也有限制,当我们要进行“操作确认”时,可能会遇到“必须冻结并重新启动…”以及“必须关闭其他正在运行的应用程序…”等情况,还需要额外小心处理。
这次在进行前端开发时,有机会考虑构建方面的问题。由于想到了”还有这种模式吗?”,于是我将它们总结起来。
前提条件
- フロントエンド の動きとして SSR(=Server Side Rendering)処理 がないこと(CSR, SSG であること)
实现的想象图
一般而言,如果是使用Next.js创建的应用程序,我想您会使用Next.js的服务器启动命令($ next start)。换句话说,应用程序将在Node.js环境中运行。
-
- $ 下一步构建
- $ 下一步启动
使用上述的方法肯定更容易,但我们正在尝试的是另一种增加了额外步骤的方法。然而,我们将尽量减少这一额外步骤的方法。
具体而言,步骤如下:
-
- 下一步构建
-
- 将构建后的文件部署到服务器(本次使用的是Go语言编写的服务器)
- 启动Go语言编写的服务器
当移动时,图像将如上所示。
唯一的不同是服务器图中的气泡(上方为「Node.js」,下方为「golang」)。
只需将已经构建完成的文件部署到golang环境中,不必要求必须是使用golang,也可以使用alpine等其他操作系统。
制作应用程序
我将创建一个Next.js应用程序。由于Next.js有很多示例,因此我认为可以参考它们来创建应用程序。还有其他创业类的文章,可以通过搜索来找到,这里就不一一介绍了。
Next.js 的配置
参照下面的公式设置,在 next.config.js 文件中设置 output: ‘export’。
执行 $ nuxt build,生成静态文件。
前端服务器的设置
如同图示所示,我们需要准备Golang的开发环境。关于设置方面,我想可以在下方等地方找到许多信息,所以我不在此详述。
关于使用Golang制作的服务器,这次我们还将使用Echo框架(选择原因没有特别的理由)。
主要.go
请复制下面的内容,并以任何文件名保存。然后执行命令 “$ go build <保存的文件名>”。
※ 我省略了例如 $ go mod init 等必要步骤。(Note: I have omitted necessary steps such as $ go mod init)
import (
"errors"
"net/http"
"net/url"
"os"
"path"
"strings"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
echoServer := echo.New()
echoServer.Use(nextjsResourceRouter)
echoServer.Start(":3000")
}
func nextjsResourceRouter(next echo.HandlerFunc) echo.HandlerFunc {
const outputRootDirName = "out"
const renderTargetFileSystem = http.Dir(outputRootDirName)
return func(c echo.Context) (err error) {
reqPath, err := getRequestPath(c)
if err != nil {
return err
}
reqName := path.Join(".", path.Clean("/"+reqPath))
file, err := renderTargetFileSystem.Open(reqName)
if err != nil {
if !os.IsNotExist(err) {
return err
}
if err = next(c); err == nil {
return nil
}
var he *echo.HTTPError
if !(errors.As(err, &he) && he.Code == http.StatusNotFound) {
return err
}
file, err = findRequestFile(renderTargetFileSystem, reqName)
if err != nil {
return err
}
}
defer file.Close()
http.ServeContent(c.Response(), c.Request(), file.Name(), file.ModTime(), file)
return nil
}
}
func getRequestPath(c echo.Context) (string, error) {
p := c.Request().URL.Path
if strings.HasSuffix(c.Path(), "*") {
p = c.Param("*")
}
return url.PathUnescape(p)
}
func findRequestFile(fs http.FileSystem, name string) (http.File, error) {
file, err := fs.Open(path.Join(".", name))
if err != nil {
file, err = fs.Open(path.Join(".", name+".html"))
}
if err != nil {
return nil, err
}
info, err := file.Stat()
if err != nil {
file.Close()
return nil, err
}
if !info.IsDir() {
return file, nil
}
file, err = fs.Open(path.Join(name, "index.html"))
if err == nil {
return file, nil
}
return fs.Open(path.Join(name, name+".html"))
}
填补1
Golang的echo框架中,有一个叫做”static”的中间件。然而,Next.js的构建方式有些特别,这个中间件并不能很好地处理。
假设有以下这样的文件夹结构。
hoge/
page.tsx
fuga/
page.tsx
piyo/
page.tsx
page.tsx
在这种情况下,将生成以下文件:”hoge.html”,”hoge/fuga.html”,”piyo.html”和”index.html”。
只要能生成”hoge/index.html”,”hoge/fuga/index.html”和”piyo/index.html”,就可以直接使用了,就不需要写这篇文章了。但是,我们需要”对预期的请求进行分配,以便返回正确的HTML文件”。
因此,我們參考並重構了靜態的中間件,使其變成了這個形式。
※ 这是最小设置,如果在正式环境下运行,我认为需要进行一些更改。
启动设置
然后,只需将”go构建的文件(即服务器)”和”Next.js构建的文件夹”放置在同一级目录,并启动服务器即可。
即使在实际部署到生产环境时,只要利用”用于Next.js构建的Docker”,”用于go服务器构建的Docker”,以及”用于运行部署的Docker”这三个多阶段构建,就可以在一个Docker文件中创建Docker镜像。
优点和缺点 hé
轻便
这个优点就像标题所写的那样是“变轻”。进行了非常简单的验证(只需要用for循环多次curl命令,然后使用$ docker stats –no-stream来查看),并计算了平均值,结果如下所示。
$ next start
30.14%139.6MiB485.8kB / 5.4MBalpine:3.17.2 で $ ./main
3.64%6.14MiB485.8kB / 6.06MB高度的可扩展性
当看到这个配置的时候,我认为有些人可能会想,“为什么不用Nginx呢?”正是如此!实际上,官方文档中也有写到,请修改Nginx的配置。
只是假设的情况,如果需要进行访问控制、额外的访问日志记录等,可能只依靠Nginx 实现会很麻烦。
如果我们像这次一样选择用golang来开发,添加服务器处理也会容易很多…这是出于这样的考虑。
虽然有需要掌握golang知识的缺点,但我认为保持可定制化的状态非常重要。
另外,请您查看源代码后会发现以下情况:如果请求的文件不存在,将首先执行路由处理,因此实现“对特定URL处理进行优先处理”的状态也变得非常容易。
费时费力
一个缺点是,在仅使用Next.js进行开发时,流程比较复杂。
作为解决之一的方法是,虽然会出现与实际生产环境不同的差异状态,
但首先使用”$ next dev”命令在Node.js上进行开发运行。
在最终确认时进行build,然后在Golang服务器上运行。
通过这样的流程,问题可能会得到一些解决。
最终
如果要给它起一个不同的标题,可以这样命名为『为 echo 创建适用于 Next.js 的静态中间件』。
虽然这次我们是以 Next.js 为基础来总结的,但是在将 HTML 和 JS 构建为客户端库(如 Svelte 等)时,也可以应用相同的方法。
如果考虑到实际环境下的运作,并且考虑到本地环境的压力是至关重要的。希望能够帮助您创建出愉快的网络应用程序。