会话 Sessions
本教程共 56 篇 · 第 54 篇 · 更新于 2026-08-07 · 约 13 分钟阅读
本节目标:掌握 Astro 7 稳定的服务端会话能力 astro:sessions,学会启用 session.driver、在页面和接口里读写会话数据,并理解它和 cookie 的区别与安全边界。
上一章讲到认证后要保存登录态,最直接的是写 cookie。但 cookie 有体积上限,而且存敏感信息不安全。Astro 7 提供了一套更得体的方案:会话(Session)。它把数据存在服务端,浏览器只拿一个不透明的会话 ID,既省了 cookie 体积,又避免了把隐私直接塞进客户端。
会话解决什么问题
普通 cookie 是在浏览器和服务端之间来回携带的一小段数据。它有两个天生的麻烦:一是体积不能大,浏览器和服务器对单条 cookie 通常有 4KB 上下的限制;二是它明晃晃地躺在客户端,存点什么都可能被看到或被篡改。
会话换了个思路:真正的用户数据(购物车、登录身份、表单草稿)存在服务端,浏览器只持有一个会话 ID。每次请求带上这个 ID,服务端就能查到对应的数据。这样既能存更多东西,又不必把敏感内容暴露给前端。
一句话区分:能放进 cookie 的小数据(比如一个用户 ID)就直接用 cookie;要存购物车、草稿、多字段资料这类稍大的东西,再上会话。会话不是要取代 cookie,而是补上 cookie 装不下、又不该裸露的那部分。
Astro 的会话只在按需渲染页面里生效,因为它依赖运行时的服务端去存取数据。静态预渲染的页面没有这个过程,自然也用不了。相关页面记得 export const prerender = false(用 output: 'server' 时这行可省略)。
---
// src/components/CartButton.astro
export const prerender = false; // 用 'server' 输出时不需要这一行
const cart = await Astro.session?.get('cart');
---
<a href="/checkout">🛒 {cart?.length ?? 0} items</a>
这里 Astro.session?.get('cart') 就是从服务端会话里取出购物车。注意那个 ?——如果会话功能没启用,Astro.session 会是 undefined,用可选链避免报错。
启用会话:配置 session.driver
会话需要一块「存储后端」来放数据,Astro 叫它 driver(驱动)。部分适配器会自动给你配好默认驱动:@astrojs/node、@astrojs/cloudflare、@astrojs/netlify 这三个适配器装好后,会话就能直接用,不用你手动指定。
如果你用的是其他适配器,就得自己在 astro.config.mjs 里写 session.driver。Astro 提供了 sessionDrivers 辅助函数,能挑一个内存或缓存型的驱动:
// astro.config.mjs
import { defineConfig, sessionDrivers } from 'astro/config';
import vercel from '@astrojs/vercel';
export default defineConfig({
adapter: vercel(),
session: {
driver: sessionDrivers.lruCache({
max: 800, // 最多缓存 800 条会话
}),
}
})
sessionDrivers.lruCache 是内存里的 LRU 缓存,适合小站点或开发期;生产环境如果要多实例共享会话,往往要换成 Redis 这类外部存储。下一节会讲怎么接。
驱动类型有哪些
会话驱动底层用的是 Unstorage 这套存储抽象,所以理论上 Unstorage 支持的驱动都能用,常见几类:
- 内存型(memory / lruCache):数据存在进程内存里,重启就丢,适合开发或单实例。
- Redis:存在外部 Redis 服务,多台服务器共享,生产常用。
- 平台自带:Cloudflare、Netlify 适配器各自提供了适配其运行环境的默认驱动。
需要接外部服务(比如 Redis)时,有个坑要注意:默认情况下驱动在构建时就配置好了,环境变量会被写死进构建产物,运行时改不了。想连外部服务,得把驱动配置单独放到一个文件里,用 entrypoint 指向它:
// src/session-driver.ts
import type { SessionDriver } from "astro";
import redisDriver from "unstorage/drivers/redis";
import { REDIS_HOST, REDIS_PORT } from "astro:env";
export default function (): SessionDriver {
return redisDriver({
host: REDIS_HOST,
port: REDIS_PORT,
});
}
// astro.config.mjs
import { defineConfig } from "astro/config";
import vercel from "@astrojs/vercel";
export default defineConfig({
adapter: vercel(),
session: {
driver: {
entrypoint: new URL('./src/session-driver.ts', import.meta.url),
}
}
})
把连接信息放到 astro:env 里,运行时才去读,这样不同环境用不同 Redis 也不会重新构建。
在页面里读写会话
在 .astro 组件和页面里,会话对象挂在全局的 Astro 上,用 Astro.session 访问。最常用的就是两个方法:get(key) 取数据、set(key, value) 存数据。
---
export const prerender = false;
// 读
const cart = await Astro.session?.get('cart');
// 写
Astro.session?.set('lastView', new Date());
---
<p>你上次访问:{String(await Astro.session?.get('lastView'))}</p>
会话数据默认没有类型约束,你可以往任意 key 里塞任意值。底层用 devalue 做序列化,支持字符串、数字、Date、Map、Set、URL、数组和纯对象——和 Astro 内容集合、Actions 用的是同一套序列化方案。
想给会话数据加类型提示(编辑器能自动补全、报错),可以在 src/env.d.ts 里声明 App.SessionData:
// src/env.d.ts
declare namespace App {
interface SessionData {
user: { id: string; name: string };
cart: string[];
}
}
声明之后,Astro.session?.get('cart') 就会是 string[] | undefined,而往 user 里塞 id: number 会被类型检查拦下。
在端点、Actions 和中间件里用
除了页面,会话在端点(API 路由)、Actions(第 45 章的表单后端)和中间件里也能用,只是访问位置不同——它们不在 Astro 全局上,而在 context 对象上,用 context.session 访问。
端点的例子,往购物车加一件商品:
// src/pages/api/addToCart.ts
import type { APIContext } from "astro";
export async function POST(context: APIContext) {
const cart = (await context.session?.get('cart')) || [];
const data = await context.request.json();
if (!data?.item) {
return new Response('Item is required', { status: 400 });
}
cart.push(data.item);
await context.session?.set('cart', cart);
return Response.json(cart);
}
中间件里很适合记录「最后访问时间」这类跨请求共享的状态:
// src/middleware.ts
import { defineMiddleware } from "astro:middleware";
export const onRequest = defineMiddleware(async (context, next) => {
context.session?.set('lastVisit', new Date());
return next();
});
会话在你第一次用到它时自动创建;想换一个新会话 ID(比如登录成功后防止会话固定攻击),调用 session.regenerate();用户登出时,调用 session.destroy() 清掉整个会话,cookie 一并失效。
7.2 新增:session: false 彻底关闭
Astro 7.2.0 加了一个开关:session: false。默认情况下,只要你用了会提供默认驱动的适配器(Node、Cloudflare、Netlify),会话运行时就会被打包进服务端。如果你压根不用会话,设 session: false 能告诉适配器「别给我配默认驱动」,从而把会话相关代码完全排除出服务端包。
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
session: false,
});
这个开关在无服务器(serverless)和边缘(edge)运行时里特别有用——包越小,冷启动越快。换句话说:用不上会话就关掉,省体积又提速。
安全注意点
会话把数据存在服务端,已经比 cookie 安全不少,但仍有几条要记牢:
- 别在会话里存明文密码、 token 密钥这类极度敏感的东西。会话虽在服务端,但它仍是一个长期有效的数据容器,万一存储被拖库就是大麻烦;密码应只存哈希,密钥应放
astro:env的服务端环境变量。 - 会话 ID 是浏览器唯一持有的凭证,要像保护 cookie 一样保护它:设
httpOnly、sameSite,最好加secure(HTTPS 才传)。 - 用户登出务必
session.destroy(),否则旧会话 ID 还能用。 - 多实例部署时,确保各实例能访问同一份会话存储(比如共用 Redis),否则用户会时灵时不灵。
小结
Astro 的会话是一块「服务端的数据抽屉」:配好 session.driver 就能用,页面里用 Astro.session、接口和中间件里用 context.session 读写。它比 cookie 能装、更安全,配合认证正好保存登录态。用不上就 session: false 关掉瘦身。敏感内容别往里塞,登出记得销毁——这几条守住了,会话就是个省心又好用的工具。