Meta
Com um servidor web Nginx, execute um aplicativo Node.js autônomo que serve páginas dinâmicas em resposta a chamadas de API (por exemplo, ../tempestvue/.next/standalone/.next/server/app/api/weather/route.js
).
Problema/questão
Com o servidor Node.js em execução via pm2
, a configuração do Nginx serve apenas uma página estática do projeto; nenhum tráfego está chegando ao servidor . O resultado é o mesmo via curl
ou navegador.
Além disso, ao inspecionar a página no navegador Dev Tools->Network Tab
, vejo erros 404 para caminhos como este: https://www.westwindwebworks.com/tempestvue/_next/static/chunks/webpack-d8cdd8b109dd43bc.js
.
Como posso atualizar a configuração para acessar os ativos 404?
Atualização: Final
Está ao vivo, VEJA AQUI !
No final das contas, o problema estava principalmente em como o standalone estava sendo construído. Para que o aplicativo standalone seja servido, www.domain.com/tempestvue
ele precisa de três coisas:
Um
next.config.mjs
com:output: "standalone",
basePath: "/tempestvue",
trailingSlash: true,
Uma estrutura de diretório como esta após a compilação:
.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
O que é realizado com este comando de construção:
"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",
Um bloco de configuração de localização de servidor para fazer proxy da porta na qual o aplicativo está escutando.
# ------------------------------
# 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';
}
Depois disso, usei ações do GitHub para copiar e descompactar build.tar.gz
na estrutura de diretório indicada abaixo na seção de segundo plano e iniciar server.js
com pm2
.
Por fim, a estrutura de diretório do servidor não precisou de ajustes, conforme contemplado na resposta aceita.
( Muito obrigado a @Ivan Shatsky por ajudar a resolver isso! )
Atualização 1:
Na produção, o navegador alcança o public
diretório e parece estar procurando pelo server.js no mesmo nível, quando na verdade está executando um nível de diretório abaixo:
/home/ec2-user/tempestvue/releases/20250128-013558/.next/standalone/server.js
Retorna https://www.westwindwebworks.com/tempestvue/_next/static/css/79b4bd61dccf868b.css
404 porque o caminho real para esse ativo é: https://www.westwindwebworks.com/tempestvue/_next/stanalone/static/css/79b4bd61dccf868b.css
?
caminhobase enext.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;
}, {}),
},
};
Fundo
No servidor de implantação, a estrutura de diretório usa um único link simbólico na pasta raiz do projeto /tempestvue
para apontar para a versão atual.
/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
Implantação
Servidor Node.js em execução pm2
aqui:
/home/ec2-user/tempestvue/releases/20250127-031305/.next/standalone/server.js
Configuração do servidor
Nota: editado para refletir a versão atualmente em uso.
# 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...
}
Testando
[ec2-user@ip-172-31-24-45 tempestvue]$ curl -i https://www.westwindwebworks.com/tempestvue/ | html-beautify
retorna uma página estática do projeto:
Nota: editado para ser breve, resposta completa no final do post.
% 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>
Com uma URL incorreta ~]$ curl -i https://www.westwindwebworks.com/tempestvu/ | html-beautify
retorna a página genérica Nginx 404 da raiz da web /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>
Resposta não editada para futuras investigações
<!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>