WHAT - Remix 入门和基本实践:理解两套产物

文章目录

在 Remix(现稳定路线为 React Router Framework Mode)中,一次构建通常生成两套产物:

目录 用途 浏览器能否直接获取
build/client JavaScript、CSS、图片、字体、预渲染 HTML 等 能,应作为静态资源公开
build/server SSR、loader、action、数据库访问等 不能,只交给服务器运行

官方构建说明也明确将 Framework Mode 的输出分成浏览器版本与服务器版本。构建说明

会进入浏览器产物的代码

以下代码会进入客户端模块图,因而应当视为公开代码:

部分 是否进入客户端
app/entry.client.tsx 是
路由的默认组件 export default 是
普通 React 组件、Hooks、事件处理代码 是
ErrorBoundary 是
HydrateFallback 是
根路由的 Layout 是
clientLoader 是
clientAction 是
clientMiddleware 是
shouldRevalidate 是
handle 通常是
meta、links 是
上述模块导入的第三方包与工具模块 是
导入的 CSS、图片、字体等资源 是

例如:

ts 复制代码
import { formatPrice } from "~/utils/price";
import Chart from "~/components/chart";

export async function clientLoader() {
  const cached = localStorage.getItem("products");
  return cached ? JSON.parse(cached) : [];
}

export default function Products({ loaderData }) {
  return <Chart data={loaderData} format={formatPrice} />;
}

这里的这些内容都可能被浏览器下载:

  • Products
  • clientLoader
  • Chart
  • formatPrice
  • 它们继续导入的依赖

Framework Mode 会自动按路由拆包。例如访问 /about 时,通常只加载根路由和 /about 对应的代码,不会立即加载 /admin。但这只是延迟下载:其他路由的客户端 chunk 依然属于公开静态资源,可以在导航或预加载时被浏览器获取。自动代码拆分

因此:

"没有在首屏加载"不等于"保密"。

任何客户端 chunk 都可以通过开发者工具下载、格式化和分析。

不会进入浏览器 JavaScript 包的代码

这些路由导出是服务端代码:

部分 是否进入客户端
app/entry.server.tsx 否
loader 否
action 否
服务端 middleware 否
headers 否
仅由这些服务端代码引用的模块 否
.server.ts、.server.tsx 否
.server/ 目录中的模块 否

例如:

ts 复制代码
import type { Route } from "./+types/project";
import { db } from "~/db.server";
import { requireUser } from "~/auth.server";

export async function loader({
  request,
  params,
}: Route.LoaderArgs) {
  const user = await requireUser(request);

  return db.project.findFirst({
    where: {
      id: params.projectId,
      ownerId: user.id,
    },
  });
}

export default function Project({ loaderData }) {
  return <h1>{loaderData.name}</h1>;
}

浏览器获得的是:

  • Project 组件代码;
  • loader 返回并被序列化的数据。

浏览器不会获得:

  • loader 函数实现;
  • db.project.findFirst;
  • 数据库连接配置;
  • requireUser 的服务端实现。

官方说明,服务端 loader 会从浏览器 bundle 中移除;.server 模块如果意外进入客户端依赖图,构建会直接失败。数据加载、.server 模块

代码不公开,不代表返回值不公开

这是最容易被忽略的一点:

ts 复制代码
export async function loader() {
  return {
    user: await getUser(),
    secret: process.env.INTERNAL_API_KEY,
  };
}

loader 函数本身不会进入浏览器,但它的返回结果会发送给浏览器,因此 secret 仍然泄漏。

同样需要视为公开数据的还有:

  • loader 返回值;
  • action 返回值;
  • 抛给错误边界的数据;
  • 写入 HTML 的初始 hydration 数据;
  • 响应头和 Cookie;
  • Resource Route 返回的 JSON、PDF、图片等;
  • 预渲染生成的 .html 与 .data 文件。

正确做法是只返回 UI 真正需要的数据:

ts 复制代码
export async function loader() {
  const user = await getUser();

  return {
    user: {
      id: user.id,
      name: user.name,
      avatarUrl: user.avatarUrl,
    },
  };
}

模块边界才是真正的判断标准

不能只看一个函数写在什么文件里,还要看它被谁导入。

下面存在泄漏风险:

ts 复制代码
// config.ts
export const databaseUrl = process.env.DATABASE_URL;
export const pageSize = 20;
tsx 复制代码
// route.tsx
import { pageSize } from "~/config";

export default function Page() {
  return <p>每页 {pageSize} 项</p>;
}

因为客户端组件导入了 config.ts,该模块进入客户端依赖图。即使构建工具可能通过 tree-shaking 删除未使用字段,也不应把安全性寄托在优化器上。

应当主动拆开:

ts 复制代码
// config.server.ts
export const databaseUrl = process.env.DATABASE_URL!;
ts 复制代码
// config.shared.ts
export const pageSize = 20;

建议约定:

text 复制代码
app/
├── db.server.ts
├── auth.server.ts
├── sessions.server.ts
├── services/
│   └── billing.server.ts
├── utils/
│   └── format-price.ts
└── routes/
    └── products.tsx

环境变量也要特别注意

在 Vite 中,以 VITE_ 开头的变量会被直接替换进客户端代码:

env 复制代码
VITE_PUBLIC_API_URL=https://api.example.com
VITE_SECRET_KEY=绝对不能这样写

所以 VITE_* 只能存公开配置,不能存密钥、数据库地址或服务端凭据。Vite 环境变量说明

静态资源同样是公开的

这些文件也支持浏览器直接获取:

  • public/ 中的所有文件;
  • 构建生成的 JS、CSS、图片和字体;
  • source map------如果启用了客户端 source map;
  • 预渲染生成的 HTML 和 .data 文件。

public/icon.png 会原样复制并通过 /icon.png 访问。Vite 静态资源说明

最终可以用这一条规则判断:

从组件、clientLoader、clientAction、clientMiddleware 或客户端入口能够到达的代码,都按公开代码处理;loader、action 和 .server 模块可以保留服务端实现,但它们发送给浏览器的任何数据仍然是公开的。

相关推荐
福兮说4 小时前
前端下载大文件,点了按钮半天没反应:fetch + blob 的三个问题和三种替代写法
前端·javascript·性能优化·文件下载
GreenTea5 小时前
我把 Agent 的 while 循环拆掉了:一种你可能没想到的 Agent 架构
前端·后端·架构
科技观察哨5 小时前
六足平台选型与纳米定位系统集成:HEB-640六自由度位移台在半导体光刻对准中的参数边界与国产替代评估
前端·人工智能
niucloud-admin5 小时前
JAVA V6 多商户商城 开发文档——web前端
前端
颜进强6 小时前
16 · NestJS LifecycleEvents 生命周期事件:五个钩子、三条边界,和 rag-server 的优雅停机
前端·后端·ai编程
今年下半年7 小时前
【VUE】整合腾讯地图、自定义区域边界、村委名称及资产统计(放大显示资产点位)
前端·javascript·vue.js
郑州光合科技余经理7 小时前
同城电商系统:库存变更怎么同步到订单
java·开发语言·前端·后端·uni-app·php·ai编程
前端snow9 小时前
ai agent --- DeepAgents 中间件
前端
data analyse 4569 小时前
报表能自定义的国内统计工具有哪些
前端·数据分析