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>
Você precisa configurar a URL raiz do seu aplicativo nas configurações do aplicativo. Caso contrário, o aplicativo gerará URLs inválidas nas páginas, o que se manifesta nos erros 404 que você vê.
O que você está tentando alcançar é comumente chamado de "hospedar um aplicativo da web sob um prefixo URI" ou "hospedar um aplicativo da web sob um caminho base diferente de root" . Embora essa tarefa possa parecer direta, na prática, ela costuma ser mais complexa do que parece. Para não me repetir, recomendo consultar esta resposta para uma explicação abrangente do porquê disso.
Supondo (olhando para a estrutura do diretório do seu aplicativo) que seu aplicativo use o framework Next.js por baixo dos panos, você precisa configurar o
basePath
parâmetro de configuração. Se ele usar alguma outra tecnologia, forneça informações adicionais sobre ela.O que eu esqueci de mencionar é que depois de configurar seu aplicativo web para usar o caminho base correto, você não precisa modificar o URI de solicitação com proxy. Em vez disso, você está colocando um esforço desnecessário para remover o
/tempestvue
prefixo do URI. Na verdade, se começarmos a analisar esse tópico em detalhes, há duas maneiras de fazer isso:A maneira correta: especificando uma parte do URI para o upstream na
proxy_pass
diretiva usando a seguinte funcionalidade nginx:Dessa forma, utilizando a seguinte combinação de diretivas
location
e :proxy_pass
Você está removendo o
/tempestvue
prefixo do URI da solicitação, mas mantendo o comportamento padrão daproxy_redirect
diretiva implícita:A maneira errada: Usando a
rewrite ... break
diretiva:Dessa forma, você faz o nginx ignorar qualquer parte do URI especificada na
proxy_pass
diretiva e evitaproxy_redirect default;
que a diretiva implícita altere a resposta:Entretanto, como mencionado anteriormente, uma vez que seu aplicativo web esteja configurado para usar o caminho base correto, você não precisa alterar o URI de solicitação com proxy:
Uma pergunta adicional do OP:
Como com sua configuração atual do nginx você enviou por proxy qualquer solicitação para seu aplicativo, incluindo solicitações de arquivos estáticos, para seu servidor Node.js, isso não fará nenhuma diferença (no entanto, seria a maneira correta para compilações de aplicativos completamente estáticos).
O que você pode fazer é servir os arquivos estáticos do seu aplicativo diretamente com o nginx, sem envolver o Node.js, para melhorar o desempenho:
Na documentação do Next.js encontrei o seguinte :
Isso provavelmente responde à sua pergunta:
Se for o caso, em vez de copiar esses diretórios, você pode tentar a seguinte configuração do nginx:
Se isso não ajudar, tente copiar esses diretórios para o
standalone
diretório, como a documentação do Next.js sugere.