Фикс 404 при обновелнии страницы

This commit is contained in:
2026-07-31 06:30:45 -07:00
parent 7af3c46bfe
commit aea0b94598
3 changed files with 58 additions and 36 deletions

View File

@@ -1,5 +1,5 @@
import { copyFile, mkdir } from 'node:fs/promises';
import { resolve } from 'node:path';
import { dirname, resolve } from 'node:path';
const root = process.cwd();
const outputDirectory = resolve(root, 'dist');
@@ -20,13 +20,17 @@ await copyFile(
resolve(serverDirectory, 'index.js'),
);
// Emit an extensionless HTML match for every client-side route. This keeps
// browser refreshes working even when the asset layer handles navigations
// before the Worker gets a chance to return the SPA fallback.
// Emit both clean-URL and directory-index shells for every client-side route.
// The directory copy also supports basic Nginx static configurations that do
// not rewrite missing paths to the root application shell.
await Promise.all(
browserRoutes.map(async (route) => {
const routeFile = resolve(outputDirectory, `${route}.html`);
await mkdir(resolve(routeFile, '..'), { recursive: true });
const routeDirectory = resolve(outputDirectory, route);
await mkdir(dirname(routeFile), { recursive: true });
await mkdir(routeDirectory, { recursive: true });
await copyFile(appShell, routeFile);
await copyFile(appShell, resolve(routeDirectory, 'index.html'));
}),
);

18
nginx.conf Normal file
View File

@@ -0,0 +1,18 @@
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location /assets/ {
try_files $uri =404;
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ /index.html;
}
}

View File

@@ -2402,101 +2402,101 @@ export const protoLayers = [
{
"id": 0,
"name": "IMAGE",
"description": ""
"description": "Изображение"
},
{
"id": 1,
"name": "MESSAGES",
"description": ""
"description": "Сообщения (forwarded/reply)"
},
{
"id": 2,
"name": "FILE",
"description": ""
"description": "Файл"
},
{
"id": 4,
"name": "CALL",
"description": ""
"description": "Звонок"
},
{
"id": 5,
"name": "VOICE",
"description": ""
"description": "Голосовое сообщение"
},
{
"id": 6,
"name": "VIDEO_CIRCLE",
"description": ""
"description": "Видео сообщение (кружок)"
},
{
"id": 7,
"name": "VIDEO",
"description": ""
"description": "Видео"
}
]
},
{
"name": "DeviceSolution",
"type": "int8",
"description": "",
"description": "Решение по девайсу, принять или отклонить вход",
"yaml": "DeviceSolution:\n type: int8\n values:\n - id: 0\n name: ACCEPT\n - id: 1\n name: DECLINE",
"values": [
{
"id": 0,
"name": "ACCEPT",
"description": ""
"description": "Принять"
},
{
"id": 1,
"name": "DECLINE",
"description": ""
"description": "Отклонить"
}
]
},
{
"name": "HandshakeStage",
"type": "int8",
"description": "",
"description": "Статус хэндшейка",
"yaml": "HandshakeStage:\n type: int8\n values:\n - id: 0\n name: COMPLETED\n - id: 1\n name: NEED_DEVICE_VERIFICATION",
"values": [
{
"id": 0,
"name": "COMPLETED",
"description": ""
"description": "Пройден"
},
{
"id": 1,
"name": "NEED_DEVICE_VERIFICATION",
"description": ""
"description": "Не пройден (приложение рендерит UI подтвердите с другого устройства)"
}
]
},
{
"name": "GroupStatus",
"type": "int8",
"description": "",
"description": "Статус в группе",
"yaml": "GroupStatus:\n type: int8\n values:\n - id: 0\n name: JOINED\n - id: 1\n name: INVALID\n - id: 2\n name: NOT_JOINED\n - id: 3\n name: BANNED",
"values": [
{
"id": 0,
"name": "JOINED",
"description": ""
"description": "В группе"
},
{
"id": 1,
"name": "INVALID",
"description": ""
"description": "Не валидная группа"
},
{
"id": 2,
"name": "NOT_JOINED",
"description": ""
"description": "Можно зайт"
},
{
"id": 3,
"name": "BANNED",
"description": ""
"description": "Заблокирован"
}
]
},
@@ -2615,23 +2615,23 @@ export const protoLayers = [
{
"name": "WebRTCType",
"type": "int8",
"description": "",
"description": "Тип WebRTC сигнала",
"yaml": "WebRTCType:\n type: int8\n values:\n - id: 0\n name: OFFER\n - id: 1\n name: ANSWER\n - id: 2\n name: ICE_CANDIDATE",
"values": [
{
"id": 0,
"name": "OFFER",
"description": ""
"description": "Offer"
},
{
"id": 1,
"name": "ANSWER",
"description": ""
"description": "Answer"
},
{
"id": 2,
"name": "ICE_CANDIDATE",
"description": ""
"description": "Estabilishment candidate"
}
]
},
@@ -2654,22 +2654,22 @@ export const protoLayers = [
{
"id": 2,
"name": "INVALID",
"description": "любая ошибка смысл которой зависит от контекста, например в случае с файловой системой - нет файла"
"description": "Любая ошибка смысл которой зависит от контекста, например в случае с файловой системой - нет файла"
},
{
"id": 3,
"name": "USERNAME_TAKEN",
"description": ""
"description": "Имя пользователя занято"
},
{
"id": 4,
"name": "OVERSIZE",
"description": "слишком большой размер файла"
"description": "Слишком большой размер файла"
},
{
"id": 5,
"name": "OVERLIFE",
"description": "слишком большой TTL"
"description": "Слишком большой TTL"
}
]
},
@@ -2716,35 +2716,35 @@ export const protoLayers = [
"name": "id",
"type": "string",
"optional": false,
"description": ""
"description": "Случайная строка"
},
{
"id": 2,
"name": "blob",
"type": "string",
"optional": true,
"description": ""
"description": "Только очень коротких типов"
},
{
"id": 3,
"name": "type",
"type": "AttachmentType",
"optional": false,
"description": ""
"description": "Тип вложения"
},
{
"id": 4,
"name": "preview",
"type": "string",
"optional": false,
"description": ""
"description": "Превью"
},
{
"id": 6,
"name": "objectId",
"type": "string",
"optional": false,
"description": ""
"description": "Выданный сервером objectId от cdn"
}
]
},