diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 18426bde..3625fed6 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -64,6 +64,8 @@ app.addHook("onSend", async (_request, reply) => { await app.register(rateLimit, { max: env.RATE_LIMIT_PER_MIN, timeWindow: "1 minute", + // Only rate-limit API endpoints — static files and the SPA fallback must never be throttled + allowList: (request) => !request.url.startsWith("/api/"), }); // Multipart upload support diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index aa0f3fae..38c51240 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,16 +1,27 @@ -import { Component, type ErrorInfo, type ReactNode } from "react"; +import { Component, type ErrorInfo, lazy, type ReactNode, Suspense } from "react"; import { BrowserRouter, Navigate, Route, Routes, useLocation } from "react-router-dom"; import { Toaster } from "sonner"; import { KeyboardShortcutProvider } from "./components/common/keyboard-shortcut-provider"; import { useAuth } from "./hooks/use-auth"; -import { AutomatePage } from "./pages/automate-page"; -import { ChangePasswordPage } from "./pages/change-password-page"; -import { FilesPage } from "./pages/files-page"; -import { FullscreenGridPage } from "./pages/fullscreen-grid-page"; -import { HomePage } from "./pages/home-page"; -import { LoginPage } from "./pages/login-page"; -import { PrivacyPolicyPage } from "./pages/privacy-policy-page"; -import { ToolPage } from "./pages/tool-page"; + +// Lazy-load all pages so each page's JS (and its icons/deps) is only +// downloaded when the user navigates there, shrinking the main bundle. +const AutomatePage = lazy(() => + import("./pages/automate-page").then((m) => ({ default: m.AutomatePage })), +); +const ChangePasswordPage = lazy(() => + import("./pages/change-password-page").then((m) => ({ default: m.ChangePasswordPage })), +); +const FilesPage = lazy(() => import("./pages/files-page").then((m) => ({ default: m.FilesPage }))); +const FullscreenGridPage = lazy(() => + import("./pages/fullscreen-grid-page").then((m) => ({ default: m.FullscreenGridPage })), +); +const HomePage = lazy(() => import("./pages/home-page").then((m) => ({ default: m.HomePage }))); +const LoginPage = lazy(() => import("./pages/login-page").then((m) => ({ default: m.LoginPage }))); +const PrivacyPolicyPage = lazy(() => + import("./pages/privacy-policy-page").then((m) => ({ default: m.PrivacyPolicyPage })), +); +const ToolPage = lazy(() => import("./pages/tool-page").then((m) => ({ default: m.ToolPage }))); class ErrorBoundary extends Component< { children: ReactNode }, @@ -92,6 +103,15 @@ function AuthGuard({ children }: { children: React.ReactNode }) { return <>{children}; } +// Single page-level loading fallback — shown while JS for a route downloads. +function PageLoader() { + return ( +
+
+
+ ); +} + export function App() { return ( @@ -99,24 +119,26 @@ export function App() { - - } /> - } /> - } /> - } /> - } /> - } /> - {/* Redirects: old color tools consolidated into adjust-colors */} - } - /> - } /> - } /> - } /> - } /> - } /> - + }> + + } /> + } /> + } /> + } /> + } /> + } /> + {/* Redirects: old color tools consolidated into adjust-colors */} + } + /> + } /> + } /> + } /> + } /> + } /> + + diff --git a/apps/web/src/components/common/review-panel.tsx b/apps/web/src/components/common/review-panel.tsx index 2aac4afb..311cc2a9 100644 --- a/apps/web/src/components/common/review-panel.tsx +++ b/apps/web/src/components/common/review-panel.tsx @@ -1,9 +1,9 @@ import { TOOLS } from "@ashim/shared"; -import * as icons from "lucide-react"; -import { ArrowRight, ChevronDown, ChevronRight, Download, Undo2 } from "lucide-react"; +import { ArrowRight, ChevronDown, ChevronRight, Download, FileImage, Undo2 } from "lucide-react"; import { useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { formatFileSize, triggerDownload } from "@/lib/download"; +import { ICON_MAP } from "@/lib/icon-map"; import { getSuggestedTools } from "@/lib/suggested-tools"; interface ReviewPanelProps { @@ -124,12 +124,8 @@ export function ReviewPanel({
{suggestedTools.map((tool) => { const ToolIcon = - ( - icons as unknown as Record< - string, - React.ComponentType<{ className?: string }> - > - )[tool.icon] || icons.FileImage; + (ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ?? + FileImage; return (