melhorias de performance
This commit is contained in:
1 parent
ce851b9cf9
commit
63d5cbebe7
4 files changed
+59
-19
No files matched your search
@@ -20,12 +20,17 @@ const app = Fastify({ logger: true, trustProxy: true });
|
|||||||
|
|
||||||
await app.register(cors, { origin: true });
|
await app.register(cors, { origin: true });
|
||||||
|
|
||||||
|
// Cache-Control é decidido no nginx interno (ver nginx/nginx.conf,
|
||||||
|
// location /models/), não aqui — "immutable"/maxAge nesse nível conflitava
|
||||||
|
// com o "no-cache" que o nginx adiciona por cima (dois headers
|
||||||
|
// Cache-Control na mesma resposta, comportamento indefinido entre
|
||||||
|
// navegadores), e ia contra o motivo do "no-cache": os .glb têm nome fixo
|
||||||
|
// mas o CONTEÚDO muda toda vez que o pipeline roda de novo.
|
||||||
await app.register(fastifyStatic, {
|
await app.register(fastifyStatic, {
|
||||||
root: MODELS_DIR,
|
root: MODELS_DIR,
|
||||||
prefix: "/models/",
|
prefix: "/models/",
|
||||||
decorateReply: false,
|
decorateReply: false,
|
||||||
immutable: true,
|
etag: true,
|
||||||
maxAge: "7d",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/api/health", async () => ({ status: "ok" }));
|
app.get("/api/health", async () => ({ status: "ok" }));
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
// - data-pipeline/seed/manifest-real.json (entradas para o seed do banco)
|
// - data-pipeline/seed/manifest-real.json (entradas para o seed do banco)
|
||||||
import { NodeIO } from "@gltf-transform/core";
|
import { NodeIO } from "@gltf-transform/core";
|
||||||
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
|
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
|
||||||
import { prune, dedup, weld, simplify } from "@gltf-transform/functions";
|
import { prune, dedup, weld, simplify, meshopt } from "@gltf-transform/functions";
|
||||||
import { MeshoptSimplifier } from "meshoptimizer";
|
import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
|
||||||
import { writeFileSync, mkdirSync } from "node:fs";
|
import { writeFileSync, mkdirSync } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
@@ -21,6 +21,7 @@ import { MUSCLE_DESCRIPTIONS } from "./muscle-descriptions.mjs";
|
|||||||
import { NERVE_DESCRIPTIONS } from "./nerve-descriptions.mjs";
|
import { NERVE_DESCRIPTIONS } from "./nerve-descriptions.mjs";
|
||||||
|
|
||||||
await MeshoptSimplifier.ready;
|
await MeshoptSimplifier.ready;
|
||||||
|
await MeshoptEncoder.ready;
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const CONVERTED_DIR = path.resolve(__dirname, "../converted");
|
const CONVERTED_DIR = path.resolve(__dirname, "../converted");
|
||||||
@@ -183,7 +184,12 @@ function classifySystem(key, rawName) {
|
|||||||
return RESPIRATORY_KEYWORDS.some((kw) => lower.includes(kw)) ? "respiratorio" : "visceral";
|
return RESPIRATORY_KEYWORDS.some((kw) => lower.includes(kw)) ? "respiratorio" : "visceral";
|
||||||
}
|
}
|
||||||
|
|
||||||
const io = new NodeIO().registerExtensions(ALL_EXTENSIONS);
|
// registerDependencies com a chave "meshopt.encoder" é o que permite ao
|
||||||
|
// io.write() de fato aplicar a compressão EXT_meshopt_compression pedida
|
||||||
|
// no transform() abaixo — sem isso, ela seria ignorada silenciosamente.
|
||||||
|
const io = new NodeIO()
|
||||||
|
.registerExtensions(ALL_EXTENSIONS)
|
||||||
|
.registerDependencies({ "meshopt.encoder": MeshoptEncoder });
|
||||||
const manifest = [];
|
const manifest = [];
|
||||||
let totalStructures = 0;
|
let totalStructures = 0;
|
||||||
|
|
||||||
@@ -349,6 +355,12 @@ for (const { fbx, key } of SOURCES) {
|
|||||||
simplify({ simplifier: MeshoptSimplifier, ratio: 0.5, error: 0.01 }),
|
simplify({ simplifier: MeshoptSimplifier, ratio: 0.5, error: 0.01 }),
|
||||||
prune(),
|
prune(),
|
||||||
dedup(),
|
dedup(),
|
||||||
|
// Comprime os atributos de vértice (posição/normal/UV) com o mesmo
|
||||||
|
// codec Meshopt já usado pra simplificação — reduz bastante o tamanho
|
||||||
|
// do .glb final (os floats crus de posição/normal dominam o arquivo),
|
||||||
|
// e o carregamento no navegador não precisa de nada extra: o useGLTF
|
||||||
|
// do drei já vem com o decoder Meshopt ligado por padrão.
|
||||||
|
meshopt({ encoder: MeshoptEncoder, level: "medium" }),
|
||||||
);
|
);
|
||||||
|
|
||||||
const outPath = path.join(MODELS_OUT_DIR, target.modelFile);
|
const outPath = path.join(MODELS_OUT_DIR, target.modelFile);
|
||||||
|
|||||||
+22
-14
@@ -1,22 +1,30 @@
|
|||||||
|
import { lazy, Suspense } from "react";
|
||||||
import { Route, Routes } from "react-router-dom";
|
import { Route, Routes } from "react-router-dom";
|
||||||
import { Layout } from "./components/Layout.js";
|
import { Layout } from "./components/Layout.js";
|
||||||
|
|
||||||
|
// A página inicial (Atlas) já carrega o peso pesado de verdade (three.js +
|
||||||
|
// r3f + drei) — separar as OUTRAS rotas em chunks à parte só evita que
|
||||||
|
// quem abre o atlas baixe/parseie de graça o código do Quiz, de Planos e
|
||||||
|
// do painel interno de estatísticas antes mesmo de ver o modelo 3D.
|
||||||
import AtlasPage from "./routes/AtlasPage.js";
|
import AtlasPage from "./routes/AtlasPage.js";
|
||||||
import PlanesPage from "./routes/PlanesPage.js";
|
const PlanesPage = lazy(() => import("./routes/PlanesPage.js"));
|
||||||
import QuizPage from "./routes/QuizPage.js";
|
const PhysicalExamPage = lazy(() => import("./routes/PhysicalExamPage.js"));
|
||||||
import PhysicalExamPage from "./routes/PhysicalExamPage.js";
|
const QuizPage = lazy(() => import("./routes/QuizPage.js"));
|
||||||
import AdminStatsPage from "./routes/AdminStatsPage.js";
|
const AdminStatsPage = lazy(() => import("./routes/AdminStatsPage.js"));
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Suspense fallback={null}>
|
||||||
<Route element={<Layout />}>
|
<Routes>
|
||||||
<Route path="/" element={<AtlasPage />} />
|
<Route element={<Layout />}>
|
||||||
<Route path="/planos-anatomicos" element={<PlanesPage />} />
|
<Route path="/" element={<AtlasPage />} />
|
||||||
<Route path="/exame-fisico" element={<PhysicalExamPage />} />
|
<Route path="/planos-anatomicos" element={<PlanesPage />} />
|
||||||
<Route path="/quiz" element={<QuizPage />} />
|
<Route path="/exame-fisico" element={<PhysicalExamPage />} />
|
||||||
</Route>
|
<Route path="/quiz" element={<QuizPage />} />
|
||||||
{/* Fora do Layout: painel interno, sem o menu/nav do atlas. */}
|
</Route>
|
||||||
<Route path="/estatisticas" element={<AdminStatsPage />} />
|
{/* Fora do Layout: painel interno, sem o menu/nav do atlas. */}
|
||||||
</Routes>
|
<Route path="/estatisticas" element={<AdminStatsPage />} />
|
||||||
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,13 @@ http {
|
|||||||
keepalive_timeout 65;
|
keepalive_timeout 65;
|
||||||
gzip on;
|
gzip on;
|
||||||
gzip_types text/plain text/css application/javascript application/json application/wasm model/gltf-binary;
|
gzip_types text/plain text/css application/javascript application/json application/wasm model/gltf-binary;
|
||||||
|
# Sem isso, o default do nginx (gzip_proxied off) desliga a compressão
|
||||||
|
# justamente nas respostas que passam por proxy_pass (/api/ e /models/,
|
||||||
|
# que são as maiores — JSON de estruturas e os .glb) — só os arquivos
|
||||||
|
# estáticos servidos direto por este nginx (JS/CSS do build) estavam
|
||||||
|
# sendo comprimidos até agora.
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_vary on;
|
||||||
|
|
||||||
# Respect a proxy in front of this container (the user's own reverse proxy).
|
# Respect a proxy in front of this container (the user's own reverse proxy).
|
||||||
set_real_ip_from 0.0.0.0/0;
|
set_real_ip_from 0.0.0.0/0;
|
||||||
@@ -72,6 +79,14 @@ http {
|
|||||||
add_header Cache-Control "no-cache";
|
add_header Cache-Control "no-cache";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Bundle do Vite (JS/CSS/fonts) tem hash de conteúdo no nome do arquivo
|
||||||
|
# — ao contrário dos .glb, um arquivo com esse nome NUNCA muda de
|
||||||
|
# conteúdo (um build novo gera nomes novos), então pode cachear "pra
|
||||||
|
# sempre" sem risco de servir algo desatualizado.
|
||||||
|
location /assets/ {
|
||||||
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
# SPA fallback.
|
# SPA fallback.
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
|
|||||||
Reference in new issue
Block a user