Files
upage/server.mjs
Takagi 8f31f82365 chore: upgrade from remix to react router v7 (#20)
* chore: upgrade from remix to react router v7

* chore: upgrade from remix to react router v7

* Refactor API routes to flat structure and simplify handlers

Migrated API route files from dynamic Remix v2 file system routing to a flat, explicit structure compatible with React Router v7. Removed all dynamic route handler files and replaced them with direct route modules under organized folders (e.g., api/chat, api/1panel, etc.). Updated route configuration in routes.ts to use the new structure and prefix helpers. Refactored all API handler modules to include authentication checks directly and removed indirection via action/loader wrappers. Updated imports and fixed references throughout the codebase to match the new file locations. Also updated Netlify deploy action endpoint and improved error handling in 1panel store.

* Refactor route handlers and update component props

Refactored several route handler functions to remove unnecessary exports and align with new conventions. Updated Chat component to receive loaderData via props and adjusted usage in chat route. Removed unused 'data' utility from routes. Minor UI component cleanup and fixed transition utility in uno.config.ts.

* adjust server abort delay

* Remove AuthErrorToast and update react-router packages

Deleted the AuthErrorToast component and its lazy loader from the codebase. Upgraded all @react-router/* and react-router dependencies from version 7.10.1 to 7.11.0 in package.json and pnpm-lock.yaml.

* Refactor route loader data typing and route IDs

Replaces manual type definitions for route loader data with types from generated Route types, improving type safety and maintainability. Updates useRouteLoaderData calls and adjusts route registration to use explicit route IDs, aligning with new type usage.

* Clean up Remix naming remnants after React Router v7 migration

* feat: add claude skills
2026-04-22 12:45:27 +08:00

103 lines
2.9 KiB
JavaScript

import { createRequestHandler } from '@react-router/express';
import compression from 'compression';
import cors from 'cors';
import express from 'express';
import rateLimit from 'express-rate-limit';
import morgan from 'morgan';
import path from 'path';
const viteDevServer =
process.env.NODE_ENV === 'production'
? undefined
: await import('vite').then((vite) =>
vite.createServer({
server: { middlewareMode: true },
}),
);
const reactRouterHandler = createRequestHandler({
build: viteDevServer
? () => viteDevServer.ssrLoadModule('virtual:react-router/server-build')
: await import('./build/server/index.js'),
});
const app = express();
app.set('trust proxy', true);
app.use(
cors({
// 允许所有来源访问,生产环境中应该设置为特定的域名
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
credentials: true,
maxAge: 86400,
}),
);
// 配置全局限流中间件
const globalLimiter = rateLimit({
windowMs: 60 * 1000, // 1 分钟
limit: 1000, // 每个 IP 每分钟最多 1000 个请求
standardHeaders: 'draft-7', // 返回标准的 RateLimit 头信息
legacyHeaders: false, // 禁用旧的 X-RateLimit 头信息
message: '请求过于频繁,请稍后再试',
proxy: true,
});
// 针对聊天 API 的特殊限流中间件
const chatApiLimiter = rateLimit({
windowMs: 60 * 1000,
limit: 5, // 每个 IP 每分钟最多 5 个聊天请求
standardHeaders: 'draft-7',
legacyHeaders: false,
message: '聊天请求过于频繁,请稍后再试',
// 仅对聊天 API 路由应用此限制
skip: (req) => !req.url.includes('/api/chat'),
proxy: true,
});
app.use((req, res, next) => {
if (req.url.startsWith('/assets') || req.url.startsWith('/build') || req.url.includes('.')) {
return next();
}
next();
});
app.use(compression());
app.use(globalLimiter);
app.use('/api/chat', chatApiLimiter);
// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
app.disable('x-powered-by');
// handle asset requests
if (viteDevServer) {
app.use(viteDevServer.middlewares);
} else {
// Vite fingerprints its assets so we can cache forever.
app.use('/assets', express.static('build/client/assets', { immutable: true, maxAge: '1y' }));
}
// Everything else (like favicon.ico) is cached for an hour. You may want to be
// more aggressive with this caching.
app.use(express.static('build/client', { maxAge: '1h' }));
// 添加对上传文件的静态服务支持
const storageDir = process.env.STORAGE_DIR || path.join(process.cwd(), 'public', 'uploads');
app.use('/uploads', express.static(storageDir, { maxAge: '1y' }));
app.use(morgan('tiny'));
// handle SSR requests
app.use(reactRouterHandler);
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Express server listening at http://localhost:${port}`);
});