fix(falukant): revalidate replaced character meshes
All checks were successful
Deploy to production / deploy (push) Successful in 1m55s

This commit is contained in:
Torsten Schulz (local)
2026-09-30 14:40:50 +02:00
parent 6d14038dad
commit c02bc07265
2 changed files with 9 additions and 2 deletions

View File

@@ -14,7 +14,10 @@ router.get('/3d/falukant/characters/:filename', async (req, res) => {
try { try {
const cachePath = await getOptimizedModelPath(filename); const cachePath = await getOptimizedModelPath(filename);
res.setHeader('Content-Type', 'model/gltf-binary'); res.setHeader('Content-Type', 'model/gltf-binary');
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); // GLB files can be replaced under the same filename during an asset deploy.
// Revalidate cached copies so clients receive a changed mesh without needing
// to clear their browser cache.
res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate');
res.sendFile(cachePath); res.sendFile(cachePath);
} catch (e) { } catch (e) {
if (e.message?.includes('Invalid model filename') || e.message?.includes('not found')) { if (e.message?.includes('Invalid model filename') || e.message?.includes('not found')) {

View File

@@ -27,6 +27,10 @@ import { getApiBaseURL } from '@/utils/axios.js';
/** Backend-Route: GET /api/models/3d/falukant/characters/:filename (Proxy mit Draco-Optimierung) */ /** Backend-Route: GET /api/models/3d/falukant/characters/:filename (Proxy mit Draco-Optimierung) */
const MODELS_API_PATH = '/api/models/3d/falukant/characters'; const MODELS_API_PATH = '/api/models/3d/falukant/characters';
const OPTIMIZED_MODEL_SUFFIX = '_opt.glb'; const OPTIMIZED_MODEL_SUFFIX = '_opt.glb';
// Bump this when deployment replaces GLB files under unchanged filenames.
// The revision is part of the URL, so a browser cannot reuse an older immutable
// response for the same model file.
const MODEL_ASSET_REVISION = '20260930-2';
// Only concrete age models are used. If an exact age is not available, choose // Only concrete age models are used. If an exact age is not available, choose
// the nearest lower one instead of the generic gender/age-group fallback. // the nearest lower one instead of the generic gender/age-group fallback.
const AVAILABLE_MODEL_AGES = Object.freeze({ const AVAILABLE_MODEL_AGES = Object.freeze({
@@ -456,7 +460,7 @@ export default {
.filter((age) => age <= modelAge) .filter((age) => age <= modelAge)
.sort((left, right) => right - left); .sort((left, right) => right - left);
const candidates = candidateAges.map((age) => const candidates = candidateAges.map((age) =>
`${prefix}/${this.actualGender}_${age}y${OPTIMIZED_MODEL_SUFFIX}` `${prefix}/${this.actualGender}_${age}y${OPTIMIZED_MODEL_SUFFIX}?v=${MODEL_ASSET_REVISION}`
); );
let gltf; let gltf;