目标
使用 Nginx Web 服务器,运行独立的 Node.js 应用程序,该应用程序响应 API 调用来提供动态页面(例如../tempestvue/.next/standalone/.next/server/app/api/weather/route.js
)。
问题
通过 运行 Node.js 服务器时pm2
,Nginx 配置仅提供来自项目的静态页面;没有流量到达服务器curl
。通过或 浏览器结果相同。
此外,在浏览器中检查页面Dev Tools->Network Tab
,我发现如下路径出现 404 错误:https://www.westwindwebworks.com/tempestvue/_next/static/chunks/webpack-d8cdd8b109dd43bc.js
。
我该如何更新配置以达到 404 资产?
更新:最终版
正在直播,请点击此处观看!
归根结底,问题主要在于如何构建独立应用。要为独立应用提供服务,www.domain.com/tempestvue
需要满足以下三个条件:
具有
next.config.mjs
:output: "standalone",
basePath: "/tempestvue",
trailingSlash: true,
构建后的目录结构如下:
.next/ standalone/ .next/ node_modules/ # Minimal set of dependencies for the app .env.production # Server-side variables package.json # Contains dependencies required to run the app server.js # The entry point for the Node.js server static/ # Static assets copied here during build process public/ # Public directory added to build.tar.gz
这是通过以下构建命令完成的:
"build:release": "NODE_ENV=production next build && cp -r .next/static .next/standalone/.next/static && npm run release && tar --exclude=.git -czf build.tar.gz .next/standalone public",
用于代理应用程序正在监听的端口的服务器位置配置块。
# ------------------------------
# Add a location block for TempestVue
# ------------------------------
location /tempestvue/ {
proxy_pass http://127.0.0.1:3000;
proxy_redirect default;
proxy_http_version 1.1;
# Recommended proxy headers:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
之后,我使用 GitHub 操作将 复制并解压build.tar.gz
到背景部分下面注明的目录结构中,然后使用server.js
启动pm2
。
最后,服务器的目录结构不需要像接受的答案中所设想的那样进行调整。
(非常感谢@Ivan Shatsky 帮助解决这个问题!)
更新 1:
在生产中,浏览器到达public
目录,似乎正在寻找同一级别的 server.js,但实际上它正在下一级目录运行:
/home/ec2-user/tempestvue/releases/20250128-013558/.next/standalone/server.js
是否https://www.westwindwebworks.com/tempestvue/_next/static/css/79b4bd61dccf868b.css
返回 404,因为该资产的真实路径是:https://www.westwindwebworks.com/tempestvue/_next/stanalone/static/css/79b4bd61dccf868b.css
?
basePath 和next.config.mjs
import dotenv from "dotenv";
import path from "path";
const isProd = process.env.NODE_ENV === "production";
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
basePath: isProd ? "/tempestvue" : "", // Adjust basePath for production
trailingSlash: true, // Recommended for apps with a basePath
//reactStrictMode: false, // Disable React strict mode
env: {
BASE_PATH: process.env.BASE_PATH,
NEXT_PUBLIC_BASE_PATH: process.env.NEXT_PUBLIC_BASE_PATH,
REACT_APP_BASE_PATH: process.env.REACT_APP_BASE_PATH,
},
webpack: (config) => {
config.optimization.minimize = false; // Ensure no minification
config.externals = [...config.externals, { canvas: "canvas" }]; // Required for Chart.js
return config;
},
};
// Determine the environment file
const envFile =
process.env.NODE_ENV === "production" ? ".env.production" : ".env.local";
console.log(`Using environment file: ${envFile}`);
// Load .env.production or .env.local
dotenv.config({ path: path.resolve(process.cwd(), envFile) });
export default {
...nextConfig,
output: "standalone",
env: {
...Object.keys(process.env)
.filter((key) => key.startsWith("NEXT_PUBLIC_")) // Only public variables are included
.reduce((env, key) => {
env[key] = process.env[key];
return env;
}, {}),
},
};
背景
在部署服务器上,目录结构使用根项目文件夹中的单个符号链接/tempestvue
指向当前版本。
/home/ec2-user/
└── tempestvue
├── 20250127-031305 -> /home/ec2-user/tempestvue/releases/20250127-031305
│ ├── ._public
│ ├── .next
│ │ ├── ._standalone
│ │ ├── ._static
│ │ ├── standalone
│ │ └── static
│ └── public
│ ├── ._TempestVue.png
│ ├── ._apple-icon copy.png
│ ├── ._favicon.svg
│ ├── ._globals.css
│ ├── ._icon.png
│ ├── ._manifest.json
│ ├── TempestVue.png
│ ├── apple-icon copy.png
│ ├── favicon.svg
│ ├── globals.css
│ ├── icon.png
│ └── manifest.json
└── releases
├── 20250126-011024
│ └── .next
├── 20250126-202835
│ └── .next
├── 20250126-220617
│ └── .next
├── 20250126-231144
│ └── .next
└── 20250127-031305
├── ._public
├── .next
└── public
部署
Node.js 服务器通过这里运行pm2
:
/home/ec2-user/tempestvue/releases/20250127-031305/.next/standalone/server.js
服务器配置
注意:已编辑以反映当前使用的版本。
# Main server block for www
server {
server_name www.westwindwebworks.com;
root /usr/share/nginx/html;
# ------------------------------
# Add a location block for TempestVue
# ------------------------------
location /tempestvue/ {
proxy_pass http://127.0.0.1:3000;
proxy_redirect default;
proxy_http_version 1.1;
# Recommended proxy headers:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
# Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
...HTTPS stuff...
}
测试
[ec2-user@ip-172-31-24-45 tempestvue]$ curl -i https://www.westwindwebworks.com/tempestvue/ | html-beautify
从项目返回一个静态页面:
注意:为简洁起见,已编辑,完整回复位于帖子末尾。
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 13307 0 13307 0 0 94041 0 --:--:-- --:--:-- --:--:-- 95050
HTTP/1.1 404 Not Found
Server: nginx/1.26.2
Date: Mon, 27 Jan 2025 12:39:03 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate
Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept-Encoding
X-Powered-By: Next.js
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/tempestvue/_next/static/css/79b4bd61dccf868b.css" data-precedence="next" />
<link rel="preload" as="script" fetchPriority="low" href="/tempestvue/_next/static/chunks/webpack-d8cdd8b109dd43bc.js" />
<script src="/tempestvue/_next/static/chunks/4bd1b696-1aaacd546f10f1e9.js" async=""></script>
<script src="/tempestvue/_next/static/chunks/517-2fa7b8a64c116b65.js" async=""></script>
<script src="/tempestvue/_next/static/chunks/main-app-dae125d39e3a4f3f.js" async=""></script>
<meta name="robots" content="noindex" />
<link rel="shortcut icon" href="/icon.png" />
<link rel="apple-touch-icon" href="/apple-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/icon.png" />
<title>404: This page could not be found.</title>
<title>TempestVue</title>
<meta name="description" content="Real-time weather visualization dashboard." />
<link rel="manifest" href="/manifest.json" />
<meta property="og:title" content="Tempest Weather | Sand Hills Beach, Scituate, MA" />
<meta property="og:description" content="Real-time weather conditions and forecast for Sand Hills Beach, Scituate, MA" />
<meta property="og:url" content="https://www.westwindwedworks.com/tempestvue/" />
<meta property="og:site_name" content="Tempest Weather" />
<meta property="og:locale" content="en_US" />
<meta property="og:image" content="https://www.westwindwedworks.com/tempestvue/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Tempest Weather | Sand Hills Beach, Scituate, MA" />
<meta name="twitter:description" content="Real-time weather conditions and forecast for Sand Hills Beach, Scituate, MA" />
<meta name="twitter:image" content="https://www.westwindwedworks.com/tempestvue/og-image.png" />
<meta name="twitter:image:width" content="1200" />
<meta name="twitter:image:height" content="630" />
<link rel="shortcut icon" href="/icon.png" />
<link rel="icon" href="/favicon-16x16.png" sizes="16x16" type="image/png" />
<link rel="icon" href="/favicon-32x32.png" sizes="32x32" type="image/png" />
<link rel="icon" href="/favicon-48x48.png" sizes="48x48" type="image/png" />
<link rel="apple-touch-icon" href="/apple-icon.png" sizes="180x180" type="image/png" />
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
<script src="/tempestvue/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script>
</head>
<body>
...
</body>
如果 URL 不正确,~]$ curl -i https://www.westwindwebworks.com/tempestvu/ | html-beautify
则会从 Web 根目录返回通用 Nginx 404 页面/usr/share/nginx/html
:
[ec2-user@ip-172-31-24-45 ~]$ curl -i https://www.westwindwebworks.com/tempestvu/ | html-beautify
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 3650 100 3650 0 0 32791 0 --:--:-- --:--:-- --:--:-- 32882
HTTP/1.1 404 Not Found
Server: nginx/1.26.2
Date: Mon, 27 Jan 2025 12:57:47 GMT
Content-Type: text/html
Content-Length: 3650
Connection: keep-alive
ETag: "6793f381-e42"
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>The page is not found</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css">
...
</style>
</head>
<body>
<h1><strong>nginx error!</strong></h1>
<div class="content">
<h3>The page you are looking for is not found.</h3>
....
</div>
</body>
未经编辑的回应,以供进一步调查
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/tempestvue/_next/static/css/79b4bd61dccf868b.css" data-precedence="next" />
<link rel="preload" as="script" fetchPriority="low" href="/tempestvue/_next/static/chunks/webpack-d8cdd8b109dd43bc.js" />
<script src="/tempestvue/_next/static/chunks/4bd1b696-1aaacd546f10f1e9.js" async=""></script>
<script src="/tempestvue/_next/static/chunks/517-2fa7b8a64c116b65.js" async=""></script>
<script src="/tempestvue/_next/static/chunks/main-app-dae125d39e3a4f3f.js" async=""></script>
<meta name="robots" content="noindex" />
<link rel="shortcut icon" href="/icon.png" />
<link rel="apple-touch-icon" href="/apple-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/icon.png" />
<title>404: This page could not be found.</title>
<title>TempestVue</title>
<meta name="description" content="Real-time weather visualization dashboard." />
<link rel="manifest" href="/manifest.json" />
<meta property="og:title" content="Tempest Weather | Sand Hills Beach, Scituate, MA" />
<meta property="og:description" content="Real-time weather conditions and forecast for Sand Hills Beach, Scituate, MA" />
<meta property="og:url" content="https://www.westwindwedworks.com/tempestvue/" />
<meta property="og:site_name" content="Tempest Weather" />
<meta property="og:locale" content="en_US" />
<meta property="og:image" content="https://www.westwindwedworks.com/tempestvue/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Tempest Weather | Sand Hills Beach, Scituate, MA" />
<meta name="twitter:description" content="Real-time weather conditions and forecast for Sand Hills Beach, Scituate, MA" />
<meta name="twitter:image" content="https://www.westwindwedworks.com/tempestvue/og-image.png" />
<meta name="twitter:image:width" content="1200" />
<meta name="twitter:image:height" content="630" />
<link rel="shortcut icon" href="/icon.png" />
<link rel="icon" href="/favicon-16x16.png" sizes="16x16" type="image/png" />
<link rel="icon" href="/favicon-32x32.png" sizes="32x32" type="image/png" />
<link rel="icon" href="/favicon-48x48.png" sizes="48x48" type="image/png" />
<link rel="apple-touch-icon" href="/apple-icon.png" sizes="180x180" type="image/png" />
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
<script src="/tempestvue/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script>
</head>
<body class="bg-gradient-to-br from-gray-950 to-gray-900 text-white min-h-screen antialiased">
<header class="w-full h-14 md:h-16 lg:h-18 px-2 sm:px-4 md:px-6 lg:px-8 py-2 md:py-2.5 border-b border-white/10 backdrop-blur-md fixed top-0 z-50 bg-gradient-to-r from-blue-400/30 via-purple-400/30 to-blue-400/30">
<div class="max-w-7xl mx-auto h-full relative">
<div class="h-full flex items-center justify-center">
<h1 class="text-lg sm:text-xl md:text-2xl lg:text-3xl xl:text-4xl font-bold text-center tracking-tight"><span class="weather-text-gradient">TempestVue v1</span></h1>
</div>
</div>
</header>
<main class="pt-14 md:pt-16 lg:pt-18">
<div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center">
<div>
<style>
body {
color: #000;
background: #fff;
margin: 0
}
.next-error-h1 {
border-right: 1px solid rgba(0, 0, 0, .3)
}
@media (prefers-color-scheme:dark) {
body {
color: #fff;
background: #000
}
.next-error-h1 {
border-right: 1px solid rgba(255, 255, 255, .3)
}
}
</style>
<h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1>
<div style="display:inline-block">
<h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2>
</div>
</div>
</div>
</main>
<script src="/tempestvue/_next/static/chunks/webpack-d8cdd8b109dd43bc.js" async=""></script>
<script>
(self.__next_f = self.__next_f || []).push([0])
</script>
<script>
self.__next_f.push([1, "1:\"$Sreact.fragment\"\n2:I[5244,[],\"\"]\n3:I[3866,[],\"\"]\n4:I[6213,[],\"OutletBoundary\"]\n6:I[6213,[],\"MetadataBoundary\"]\n8:I[6213,[],\"ViewportBoundary\"]\na:I[4835,[],\"\"]\n:HL[\"/tempestvue/_next/static/css/79b4bd61dccf868b.css\",\"style\"]\n"])
</script>