Chapter 06

数据获取与 SSR 模式

掌握 frontmatter 数据获取、SSR 服务端渲染配置、API 路由创建与环境变量管理

在 Frontmatter 中获取数据

静态模式的数据获取

Astro 默认以静态生成(SSG)模式工作——构建时执行 frontmatter 代码,生成静态 HTML。这意味着你可以在 frontmatter 中直接调用任何 Node.js API,包括文件系统、数据库驱动等。

---
// 构建时执行,可以调用任何 Node.js API
import fs from 'node:fs/promises';
import { db } from '../lib/db';

// fetch API(Node 18+ 内置)
const response = await fetch('https://api.github.com/repos/withastro/astro');
const repo = await response.json();

// 读取本地文件
const config = JSON.parse(await fs.readFile('./config.json', 'utf-8'));

// 查询数据库(构建时)
const featuredPosts = await db.post.findMany({
  where: { featured: true },
  take: 3,
});
---

<h1>GitHub Stars: {repo.stargazers_count}</h1>
<ul>
  {featuredPosts.map(p => <li>{p.title}</li>)}
</ul>
静态模式的限制

在 output: 'static' 模式下,数据只在构建时获取一次。如果 API 数据更新了,需要重新构建网站。对于频繁变化的数据,需要使用 SSR 模式或结合增量静态再生(ISR)。

启用 SSR:服务端渲染模式

安装适配器

# Vercel(推荐)
npx astro add vercel

# Netlify
npx astro add netlify

# Node.js 自托管
npx astro add node

# Cloudflare Workers/Pages
npx astro add cloudflare
// astro.config.mjs - SSR 配置
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  output: 'server',    // 全站 SSR
  adapter: vercel(),
});

// 或者混合模式:默认 SSG,个别页面启用 SSR
export default defineConfig({
  output: 'hybrid',    // 混合模式(Astro 2.6+)
  adapter: vercel(),
});

SSR 页面中的请求对象

---
// 在 output: 'server' 或 output: 'hybrid' + export const prerender = false 时
export const prerender = false;  // 混合模式时明确声明此页面为 SSR

// Astro.request:当前请求的 Request 对象
const url = new URL(Astro.request.url);
const searchQuery = url.searchParams.get('q') ?? '';

// 读取请求头
const userAgent = Astro.request.headers.get('user-agent');

// 读取 Cookie
const cookie = Astro.cookies.get('session');
const sessionId = cookie?.value;

// 读取 POST 请求 body
if (Astro.request.method === 'POST') {
  const formData = await Astro.request.formData();
  const username = formData.get('username');
}

// 设置响应头
Astro.response.headers.set('Cache-Control', 'private, max-age=0');
---

<p>搜索:{searchQuery}</p>

API 路由

创建 API 端点

// src/pages/api/users.ts
import type { APIRoute } from 'astro';
import { db } from '../../lib/db';

export const GET: APIRoute = async ({ request, params }) => {
  const users = await db.user.findMany();
  return new Response(JSON.stringify(users), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
};

export const POST: APIRoute = async ({ request }) => {
  const body = await request.json();
  const user = await db.user.create({ data: body });
  return new Response(JSON.stringify(user), { status: 201 });
};

环境变量

# .env 文件
DATABASE_URL=postgresql://localhost/mydb
API_SECRET=your-secret-key-here

# PUBLIC_ 前缀的变量可在客户端访问
PUBLIC_API_BASE_URL=https://api.example.com
---
// 服务端(frontmatter)可访问所有环境变量
const dbUrl = import.meta.env.DATABASE_URL;
const secret = import.meta.env.API_SECRET;

// PUBLIC_ 前缀的变量在客户端也可访问
const apiBase = import.meta.env.PUBLIC_API_BASE_URL;
---
安全警告:不要在客户端暴露密钥

不带 PUBLIC_ 前缀的环境变量(如 DATABASE_URLAPI_SECRET)只在服务端 frontmatter 中可访问,不会被打包到客户端 JS 中。带 PUBLIC_ 前缀的变量会嵌入到客户端 JS 中,所有用户都能在浏览器中查看,切勿存放密钥。

SSG vs SSR 的核心区别

静态生成(SSG)- 默认
数据在构建时获取,生成静态 HTML 文件。优点:极快的响应速度(CDN 缓存)、无服务器成本;缺点:数据更新需重新构建,不能读取请求参数。适合博客、文档、营销页。
服务端渲染(SSR)- output: server
每次请求动态执行 frontmatter,生成 HTML。优点:可读取 Cookie/请求参数、数据始终是最新的;缺点:需要服务器运行时、响应速度受 API 延迟影响。适合用户个性化内容、搜索结果、实时数据。
混合模式(Hybrid)- output: hybrid
默认 SSG,用 export const prerender = false 将特定页面/API 切换为 SSR。适合大部分内容静态、少数页面需要动态数据的场景(如博客 + 用户评论 API)。Astro 推荐的生产最佳实践。

Astro.locals:SSR 中间件通信

// src/middleware.ts(Astro 中间件)
import { defineMiddleware } from 'astro:middleware';
import { verifyJWT } from './lib/auth';

// 中间件在每个请求时运行,可以向 locals 注入数据
export const onRequest = defineMiddleware(async (context, next) => {
  const token = context.cookies.get('session')?.value;

  if (token) {
    const user = await verifyJWT(token);
    // 将用户信息注入到 locals,所有 Astro 页面/API 都能访问
    context.locals.user = user;
  }

  // 保护路由:未登录则重定向
  if (context.url.pathname.startsWith('/dashboard') && !context.locals.user) {
    return context.redirect('/login');
  }

  return next();
});

// 在页面中使用 locals
// const { user } = Astro.locals;

Astro Actions:类型安全的服务端函数

Astro Actions(Astro 5 稳定版特性)是一种在服务端定义、在客户端调用的 RPC 机制。不同于传统 API 路由,Actions 提供全链路 TypeScript 类型安全:输入参数由 zod 验证,返回值类型自动推断,客户端调用无需手写 fetch 代码。

Astro Actions
Astro 5 引入的类型安全服务端函数。在 src/actions/index.ts 中定义,通过 astro:actions 导入后在客户端或表单中调用。与手写 API Route 相比,消除了 fetch URL 硬编码、手动解析 body、类型不匹配等问题。
defineAction
用于定义单个 Action 的函数。接受 input(zod 验证 schema)和 handler(服务端处理逻辑)。handler 中可以访问数据库、Session、发送邮件等服务端资源。
ActionError
Actions 的标准错误类型,包含 code(标准错误码如 BAD_REQUEST/UNAUTHORIZED)和 message。客户端通过解构返回值中的 error 字段获取,不需要 try/catch。

定义 Actions

// src/actions/index.ts
import { defineAction, ActionError } from 'astro:actions';
import { z } from 'astro:schema';

export const server = {
  // 发送联系表单的 Action
  contact: defineAction({
    // input 使用 zod 验证,自动生成 TypeScript 类型
    input: z.object({
      name: z.string().min(1, '姓名不能为空'),
      email: z.string().email('邮箱格式不正确'),
      message: z.string().min(10, '消息至少 10 个字符'),
    }),
    // handler 在服务端执行,可访问数据库、发送邮件等
    handler: async ({ name, email, message }) => {
      const result = await db.contact.create({
        data: { name, email, message, createdAt: new Date() }
      });
      // 发送通知邮件
      await sendEmail({ to: 'admin@example.com', subject: `新留言来自 ${name}` });
      return { id: result.id, success: true };
    },
  }),

  // 需要认证的 Action
  updateProfile: defineAction({
    input: z.object({
      displayName: z.string().optional(),
      bio: z.string().max(200).optional(),
    }),
    handler: async (input, context) => {
      // context 包含 request、cookies、locals(中间件注入的数据)
      const user = context.locals.user;
      if (!user) {
        throw new ActionError({
          code: 'UNAUTHORIZED',
          message: '请先登录',
        });
      }
      return db.user.update({ where: { id: user.id }, data: input });
    },
  }),
};

在客户端调用 Actions

---
// src/pages/contact.astro
import { actions } from 'astro:actions';
---

<form>
  <input name="name" placeholder="姓名" required />
  <input name="email" type="email" placeholder="邮箱" required />
  <textarea name="message" placeholder="留言"></textarea>
  <button type="submit">发送</button>
</form>

<script>
import { actions } from 'astro:actions';

document.querySelector('form')?.addEventListener('submit', async (e) => {
  e.preventDefault();
  const formData = new FormData(e.target as HTMLFormElement);

  // 类型安全调用:参数类型由 zod schema 推断,返回值类型自动推断
  const { data, error } = await actions.contact(formData);

  if (error) {
    // error.code 是字符串枚举(BAD_REQUEST / UNAUTHORIZED 等)
    console.error(error.message);
    return;
  }

  // data 类型完全推断自 handler 返回值
  console.log(`提交成功,ID: ${data.id}`);
});
</script>
Actions vs API Routes 的选择

Actions 适合表单提交、RPC 风格调用(明确的输入/输出),以及需要 TypeScript 端到端类型安全的场景。API Routes 更灵活,适合:需要精细控制 HTTP 响应格式、实现 RESTful 资源端点(供外部调用)、或处理文件上传流等低级别 HTTP 操作的场景。两者可以在同一项目中共存。

Actions 需要 SSR 支持

Astro Actions 在底层通过 POST 请求到特殊端点实现,因此需要服务器运行时(安装适配器并开启 output: 'server' 或 output: 'hybrid')。纯静态模式(output: 'static')无法使用 Actions——表单提交后无处理服务器。在 Cloudflare Workers 等边缘环境中,Actions 工作正常,但需要确保 handler 中不使用 Node.js 专有 API。

本章小结

本章核心要点