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

文章目录

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

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

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

会进入浏览器产物的代码

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

部分 是否进入客户端
app/entry.client.tsx
路由的默认组件 export default
普通 React 组件、Hooks、事件处理代码
ErrorBoundary
HydrateFallback
根路由的 Layout
clientLoader
clientAction
clientMiddleware
shouldRevalidate
handle 通常是
metalinks
上述模块导入的第三方包与工具模块
导入的 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 静态资源说明

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

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

相关推荐
mayaairi2 小时前
Vue2 生命周期完全指南:从创建到销毁的8个钩子函数
前端·javascript·vue.js
xy34532 小时前
axure9.0 如何打造一个计时器(简单版)
前端·ui·html·axure·原型·产品设计
IMPYLH3 小时前
HTML 的 <pre> 元素
前端·javascript·html
tsumikistep4 小时前
【前端】Vue + Axios 跨域问题实战:7070 代理转发 8080 + 401 报错分析(黑马外卖)
前端·javascript·vue.js
lichenyang4534 小时前
鸿蒙首页从等高商品 Grid 到双列瀑布流:同一张图也能做出小红书式浏览节奏
前端
里欧跑得慢4 小时前
Flutter主题与样式详解
前端·css·flutter·web
计算机魔术师4 小时前
TPU推理性价比翻50%,NVIDIA的护城河要见底了?
前端
IT_陈寒4 小时前
Python的多线程就是个假把式,我算是体验到了
前端·人工智能·后端
OpenTiny社区5 小时前
【直播分享】GenUI SDK 技术公开课第二讲 | GenuiChat 核心配置深度解析
前端·github