打开网易新闻 查看精彩图片

Fresh 1.0发布时,Deno团队放了个数据:默认配置下,你的首屏JavaScript体积是0KB。不是压缩后,不是gzip后,是字面意义上的零。这在React生态里相当于说"我们不做SPA了"——但Fresh偏偏又支持交互组件,只是换了个叫"Islands(岛屿)"的玩法。

Ryan Dahl(Deno创始人)在2022年的演讲里提过,他后悔Node.js的模块系统。Fresh像是他对前端架构的第二次"后悔":既然SPA(单页应用,Single Page Application)把整包JS塞给用户的模式有问题,能不能让页面大部分是静态HTML,只有需要交互的地方才"活"过来?

零JS不是噱头,是架构层面的取舍

零JS不是噱头,是架构层面的取舍

Fresh的默认行为是服务端渲染(SSR,Server-Side Rendering)纯HTML。你写一个路由组件,编译后用户收到的就是干净标签,没有hydration(注水,即把静态HTML转化为可交互组件的过程),没有虚拟DOM diff,没有客户端路由劫持。

代码层面看,一个基础页面长这样:

// routes/index.tsx — 这段代码运行后,浏览器收到的HTML里没有任何script标签

export default function Home() {return (

Welcome to Fresh

This page ships ZERO JavaScript.

对比Next.js的默认行为:哪怕是最简单的页面,Next也会给你塞_runtime和_react-dom。Fresh的"零"是彻底的不加载,不是懒加载,不是代码分割,是直接没有。

代价是你得接受MPA(多页应用,Multi-Page Application)的导航体验——每次跳转都是完整页面刷新。Fresh团队认为这对内容型站点是feature不是bug:浏览器原生导航比客户端路由快,且没有状态同步的心智负担。

Islands架构:哪里需要交互,哪里长出水

Islands架构:哪里需要交互,哪里长出水

但现代网站不可能完全静态。Fresh的解法是把页面拆成"海洋"和"岛屿":海洋是服务端渲染的静态HTML,岛屿是客户端hydrate的交互组件。只有放在islands/目录下的组件会被打包、发送到浏览器。

看个计数器例子:

// islands/Counter.tsx — 这个组件会被单独打包,体积通常<5KB(Preact+Signals)

打开网易新闻 查看精彩图片

import { useSignal } from '@preact/signals';export default function Counter() {const count = useSignal(0);return (

Count: {count}

count.value++}>Increment

然后在页面里引入:

// 只有Counter组件需要JS,其余部分仍是纯HTML

import Counter from '../islands/Counter.tsx';export default function Home() {return (

Static content (no JS)

This paragraph is pure HTML.

{/* 岛屿在这里hydrate */}

Fresh用Preact(轻量版React,体积约3KB)做岛屿的运行时,配合Signals做状态管理。整个岛屿的hydration是自动的:框架会扫描HTML,找到data-fresh-id标记的节点,注入对应组件。

关键设计:岛屿之间默认不通信。每个岛屿是独立的生命周期,避免了React里常见的"一个状态变化触发全树rerender"问题。如果需要跨岛屿通信,得显式用URL参数、localStorage或自定义事件——这种"不方便"是故意的,逼你思考哪些交互真的需要耦合。

数据流:从Handler到组件的显式管道

数据流:从Handler到组件的显式管道

Fresh没有getServerSideProps或loader这种"魔法"数据获取。每个路由可以导出一个handler对象,处理HTTP请求,然后把数据显式注入页面组件。

用户详情页的典型写法:

// 类型安全贯穿请求处理到组件渲染

import { Handlers, PageProps } from '$fresh/server.ts';interface User {id: string;name: string;email: string;}export const handler: Handlers = {async GET(req, ctx) {const user = await db.getUser(ctx.params.id);if (!user) return ctx.renderNotFound();return ctx.render(user); // 数据直接塞进组件props},};export default function UserPage({ data }: PageProps) {return (

{data.name}

{data.email}

ctx.renderNotFound()会触发404页面,ctx.render()则把序列化后的数据塞进HTML。组件通过PageProps拿到类型安全的数据,没有 suspense、没有streaming、没有渐进式加载——就是一次请求,一份HTML。

API路由用同一套Handlers范式,只是返回Response对象而非渲染组件:

打开网易新闻 查看精彩图片

// 同一文件里可以并存页面路由和API路由,靠文件位置区分

export const handler: Handlers = {async GET() {const users = await db.getUsers();return new Response(JSON.stringify(users), {headers: { 'Content-Type': 'application/json' },});},async POST(req) {const body = await req.json();const user = await db.createUser(body);return new Response(JSON.stringify(user), { status: 201 });},};

没有/api前缀的强制约定,routes/api/users.ts就是/api/users,routes/users/[id].tsx就是/users/123。文件系统即路由,但比Next.js更严格:Fresh不支持可选参数、不支持正则匹配,复杂路由得用中间件拦截。

中间件和边缘部署:Deno原生优势的兑现

Fresh的中间件写法类似Koa,支持异步拦截和响应修改:

// 全局中间件,计算响应时间并注入header

export async function handler(req: Request, ctx: MiddlewareHandlerContext) {const start = Date.now();const response = await ctx.next();const duration = Date.now() - start;response.headers.set('X-Response-Time', `${duration}ms`);return response;}

因为跑在Deno上,Fresh天然支持Deno Deploy(Deno的边缘计算平台)。一个Fresh应用可以部署到全球35个边缘节点,冷启动时间控制在50ms以内——这比Vercel的Node.js runtime快一个数量级,因为Deno不需要打包整个node_modules。

Deno Deploy的免费 tier 包含每天10万次请求,对于内容站或MVP产品足够用。Fresh的"零JS默认"在这里变成真金白银的优势:边缘节点返回的HTML越小,缓存命中率越高,带宽成本越低。

谁该用,谁不该用

谁该用,谁不该用

Fresh的适用场景很清晰:内容型网站、营销页面、文档站点、电商商品页——任何首屏以阅读为主、交互分散且独立的场景。Deno官网、Fresh自己的文档站、包括部分Stripe文档都跑在Fresh上。

不适合的场景同样清晰:后台管理系统(大量表单联动)、实时协作工具(WebSocket密集)、复杂仪表盘(大量客户端状态)。这些场景用Next.js或Remix更舒服,Fresh的岛屿架构会变成束缚。

一个有趣的对比:Astro也做Islands架构,但Astro是"多框架"的,支持React/Vue/Svelte混合。Fresh是Deno生态的"官方答案",绑定Preact,工具链更薄,但生态也更封闭。2023年Fresh 1.2加入了对npm specifiers的支持,可以用import map桥接部分Node生态,但迁移成本仍在。

Fresh的GitHub仓库现在有15k star,周下载量约2万次——和Next.js的千万级比可以忽略,但在Deno生态里是头部框架。Ryan Dahl在2024年初的访谈里说,Fresh的目标不是替代Next.js,而是证明"有一种更简单的全栈方式存在"。

现在的问题是:当你的团队已经在Vercel上跑熟了Next.js,边缘部署的50ms优势,值得换一套运行时、重新培训、放弃npm生态的便利性吗?Deno的答案是"从边缘项目开始试",但大多数公司的边缘项目,可能永远不会到来。