架构
Fresh 是一个服务器优先的 Web 框架。页面在服务器上渲染,只有交互部分(岛屿)将 JavaScript 发送到浏览器。本页解释了请求如何流经框架。
请求生命周期
核心概念
服务器优先渲染
每个页面在发送到浏览器之前都在服务器上完全渲染为 HTML。这意味着:
- 页面立即可见 - 没有空白加载屏幕
- 搜索引擎可以看到完整内容
- 页面在没有启用 JavaScript 的情况下也能工作
岛屿架构
Fresh 使用 岛屿架构。只有 islands/ 目录中的组件在浏览器中水合。其他所有内容都是静态 HTML,永远不会在客户端运行 JavaScript。
这意味着一个只有单个交互按钮的页面只发送该按钮的 JavaScript - 而不是整个页面的 JavaScript。
中间件链
中间件按注册顺序执行,包装处理程序。每个中间件调用 ctx.next() 将控制权传递给下一个中间件(或处理程序)。这创建了一个类似洋葱的模式,中间件可以在请求(ctx.next() 之前)和响应(ctx.next() 之后)上都起作用:
ts
app.use(async (ctx) => {
// 之前:在请求进入时运行
console.log("请求:", ctx.url.pathname);
const response = await ctx.next();
// 之后:在响应离开时运行
console.log("状态:", response.status);
return response;
});作用域中间件仅对匹配特定路径前缀的请求运行。将路径模式作为第一个参数传递给 app.use():
ts
app.use("/admin/*", async (ctx) => {
// 仅对 /admin/* 路由运行
const user = ctx.state.user;
if (!user?.isAdmin) return new Response("禁止访问", { status: 403 });
return ctx.next();
});全局中间件在每个请求上运行;作用域中间件让你将身份验证或日志记录等逻辑应用到路由的子集。
布局继承
布局包装页面组件并从父目录继承。routes/blog/post.tsx 的页面从以下位置继承布局:
routes/_layout.tsx(根布局)routes/blog/_layout.tsx(区块布局)
布局从外到内嵌套:根布局是最外层,每个更深的布局包装得更靠近页面,最内层的布局直接包装页面组件。应用包装器 (_app.tsx) 包装所有内容。
构建和部署
Fresh 使用 Vite 为生产环境打包岛屿 JavaScript。 deno task build 命令:
- 发现所有岛屿及其依赖项
- 使用代码分割打包客户端 JavaScript
- 生成服务器入口点 (
_fresh/server.js) - 为缓存破坏对资源进行哈希处理
在生产环境中,_fresh/server.js 提供预构建的资源。在开发环境中,Vite 提供热模块替换以实现即时反馈。