Remix是一个既能做服务端渲染,又能做单页应用的框架,如果想做单页应用,又想学服务端渲染,使用Remix可以降低学习成本。最近,在学习Remix的过程中,遇到了在SPA模式下与Ant Design整合的问题。
我用Remix官网的命令下载了一个SPA的模板
npx create-remix@latest --template remix-run/remix/templates/spa
我们知道,Remix有一个ErrorBoundary组件,是用来显示错误页的,比如404页面,我在root.tsx导出了这个组件
export function ErrorBoundary(){const navigate=useNavigate()return (<Resultstatus="404"title="404"subTitle="抱歉,你访问的页面不存在。"extra={<Button type="primary" onClick={() => navigate(-1)}>返回</Button>}/>)
}
这里面使用了Ant Design的Result组件,用来显示404错误信息,结果在测试过程中发现页面卡死,CPU温度飙高,浏览器控制台不停报错,而且页面中样式也没加载出来,排版混乱。
后来,我花了几天研究这个问题,发现是root.tsx中的Layout组件搞的鬼。Layout组件是用来定义root.tsx中的布局的,它会把ErrorBoundary组件当作静态内容给放进去,因此无法动态加载Ant Design组件对应的样式,并且出现一大堆报错,导致CPU空转,温度飙高。因此,我注释了这个组件,
// export function Layout({ children }: { children: React.ReactNode }) {
// return (
// <html lang="en">
// <head>
// <meta charSet="utf-8" />
// <meta name="viewport" content="width=device-width, initial-scale=1" />
// <Meta />
// <Links />
// </head>
// <body>
// {children}
// <ScrollRestoration />
// <Scripts />
// </body>
// </html>
// );
// }
后来在Remix官网找到了解决办法。
首先,在app目录下新建index.html
<!DOCTYPE html>
<html lang="en">
<head><!-- <title>My Cool App!</title> --><meta charSet="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
</head><body><div id="app"><!-- Remix SPA --></div>
</body></html>
这里面建议不要有title标签,否则Remix给不同的路由添加的title会失效。然后,修改root.tsx
export default function App() {return (<><html lang="en"><head><meta charSet="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><Meta /><Links /></head><body><Outlet/><ScrollRestoration /><Scripts /></body></html></>);
}export function HydrateFallback() {return (<><ProSkeleton type="list"></ProSkeleton><Scripts/></>)
}
其中,App组件是用来加载路由组件的,里面需要包含Meta 和Links 标签,才能使每个路由导出的Meta和Links生效,保证样式文件能够正常加载,HydrateFallback组件是在路由跳转过程中显示加载状态的页面,如骨架屏。
接着,在app下创建entry.server.tsx
import fs from "node:fs";
import path from "node:path";import type { EntryContext } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { renderToString } from "react-dom/server";export default function handleRequest(request: Request,responseStatusCode: number,responseHeaders: Headers,remixContext: EntryContext
) {const shellHtml = fs.readFileSync(path.join(process.cwd(), "app/index.html")).toString();const appHtml = renderToString(<RemixServer context={remixContext} url={request.url} />);const html = shellHtml.replace("<!-- Remix SPA -->",appHtml);return new Response(html, {headers: { "Content-Type": "text/html" },status: responseStatusCode,});
}
修改app下的client.entry.tsx
import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";startTransition(() => {hydrateRoot(document.querySelector("#app") || document,<StrictMode><RemixBrowser/></StrictMode>);
});
这样,就能把路由组件加载到index.html中的
<div id="app"><!-- Remix SPA --></div>
了。
然后,我测试了一下,发现404页面能够正常跳转,没有样式加载错误了。