动态数据 #
一、SSR 模式概述 #
1.1 启用 SSR #
在 astro.config.mjs 中配置:
javascript
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/server';
export default defineConfig({
output: 'server',
adapter: vercel(),
});
1.2 SSR vs 静态生成 #
| 特性 | 静态生成 (SSG) | 服务端渲染 (SSR) |
|---|---|---|
| 数据获取时机 | 构建时 | 请求时 |
| 内容更新 | 需要重新构建 | 实时更新 |
| 响应速度 | 极快 | 较快 |
| 服务器需求 | 无 | 需要 |
| 适用场景 | 内容网站 | 动态应用 |
二、请求时数据获取 #
2.1 基本用法 #
在 SSR 模式下,每次请求都会执行 Frontmatter 中的代码:
astro
---
// src/pages/dashboard.astro
const response = await fetch('https://api.example.com/stats');
const stats = await response.json();
---
<div class="dashboard">
<h1>实时统计</h1>
<p>访问量: {stats.views}</p>
<p>用户数: {stats.users}</p>
<p>更新时间: {new Date().toLocaleString('zh-CN')}</p>
</div>
2.2 请求参数处理 #
astro
---
// src/pages/search.astro
const query = Astro.url.searchParams.get('q') || '';
const page = parseInt(Astro.url.searchParams.get('page') || '1');
const response = await fetch(
`https://api.example.com/search?q=${encodeURIComponent(query)}&page=${page}`
);
const results = await response.json();
---
<div>
<form>
<input type="text" name="q" value={query} />
<button type="submit">搜索</button>
</form>
{query && (
<div>
<p>搜索 "{query}" 的结果:</p>
{results.items.map(item => (
<div key={item.id}>{item.title}</div>
))}
</div>
)}
</div>
2.3 POST 请求处理 #
astro
---
// src/pages/api/contact.astro
if (Astro.request.method === 'POST') {
const data = await Astro.request.json();
const response = await fetch('https://api.example.com/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (response.ok) {
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
}
return new Response(JSON.stringify({ error: 'Failed' }), {
status: 500,
});
}
return new Response('Method not allowed', { status: 405 });
---
三、混合渲染模式 #
3.1 启用混合模式 #
javascript
// astro.config.mjs
export default defineConfig({
output: 'hybrid',
});
3.2 预渲染静态页面 #
astro
---
// src/pages/blog/index.astro
export const prerender = true;
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
---
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
3.3 动态渲染页面 #
astro
---
// src/pages/dashboard.astro
export const prerender = false;
const stats = await fetch('https://api.example.com/stats').then(r => r.json());
---
<div>
<h1>实时数据</h1>
<p>{stats.value}</p>
</div>
四、缓存策略 #
4.1 响应缓存 #
astro
---
// src/pages/api/data.astro
const response = await fetch('https://api.example.com/data', {
headers: {
'Cache-Control': 'max-age=3600',
},
});
const data = await response.json();
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=3600',
},
});
---
4.2 自定义缓存 #
typescript
// src/lib/cache.ts
interface CacheEntry<T> {
data: T;
timestamp: number;
ttl: number;
}
class MemoryCache {
private cache = new Map<string, CacheEntry<any>>();
get<T>(key: string): T | null {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() - entry.timestamp > entry.ttl) {
this.cache.delete(key);
return null;
}
return entry.data;
}
set<T>(key: string, data: T, ttl: number): void {
this.cache.set(key, {
data,
timestamp: Date.now(),
ttl,
});
}
delete(key: string): void {
this.cache.delete(key);
}
clear(): void {
this.cache.clear();
}
}
export const cache = new MemoryCache();
4.3 使用缓存 #
astro
---
// src/pages/news.astro
import { cache } from '../lib/cache';
const cacheKey = 'news-data';
const ttl = 5 * 60 * 1000; // 5分钟
let news = cache.get(cacheKey);
if (!news) {
news = await fetch('https://api.example.com/news').then(r => r.json());
cache.set(cacheKey, news, ttl);
}
---
<div>
{news.map(item => (
<article key={item.id}>{item.title}</article>
))}
</div>
五、Cookie 和 Session #
5.1 读取 Cookie #
astro
---
// src/pages/dashboard.astro
const cookies = Astro.cookies;
const token = cookies.get('token')?.value;
const userId = cookies.get('userId')?.value;
if (!token) {
return Astro.redirect('/login');
}
const user = await fetch(`https://api.example.com/users/${userId}`, {
headers: {
'Authorization': `Bearer ${token}`,
},
}).then(r => r.json());
---
<div>
<h1>欢迎, {user.name}</h1>
</div>
5.2 设置 Cookie #
astro
---
// src/pages/login.astro
if (Astro.request.method === 'POST') {
const { email, password } = await Astro.request.json();
const response = await fetch('https://api.example.com/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const { token, user } = await response.json();
Astro.cookies.set('token', token, {
path: '/',
maxAge: 60 * 60 * 24 * 7,
httpOnly: true,
secure: true,
sameSite: 'strict',
});
Astro.cookies.set('userId', user.id, {
path: '/',
maxAge: 60 * 60 * 24 * 7,
});
return Astro.redirect('/dashboard');
}
---
5.3 Session 管理 #
typescript
// src/lib/session.ts
import { createHash, randomBytes } from 'crypto';
interface SessionData {
userId: string;
createdAt: number;
expiresAt: number;
}
const sessions = new Map<string, SessionData>();
function generateSessionId(): string {
return randomBytes(32).toString('hex');
}
function createSession(userId: string, ttl = 60 * 60 * 24): string {
const sessionId = generateSessionId();
const now = Date.now();
sessions.set(sessionId, {
userId,
createdAt: now,
expiresAt: now + ttl * 1000,
});
return sessionId;
}
function getSession(sessionId: string): SessionData | null {
const session = sessions.get(sessionId);
if (!session) return null;
if (Date.now() > session.expiresAt) {
sessions.delete(sessionId);
return null;
}
return session;
}
function deleteSession(sessionId: string): void {
sessions.delete(sessionId);
}
export { createSession, getSession, deleteSession };
六、实时数据 #
6.1 轮询数据 #
astro
---
// src/pages/live-stats.astro
const stats = await fetch('https://api.example.com/stats/live').then(r => r.json());
---
<div id="live-stats">
<h1>实时统计</h1>
<p>在线用户: <span id="online-users">{stats.onlineUsers}</span></p>
<p>当前请求: <span id="requests">{stats.requests}</span></p>
</div>
<script>
async function updateStats() {
const response = await fetch('/api/stats/live');
const stats = await response.json();
document.getElementById('online-users').textContent = stats.onlineUsers;
document.getElementById('requests').textContent = stats.requests;
}
setInterval(updateStats, 5000);
</script>
6.2 API 端点 #
astro
---
// src/pages/api/stats/live.astro
const stats = await fetch('https://api.example.com/stats/live').then(r => r.json());
return new Response(JSON.stringify(stats), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
},
});
---
七、用户认证 #
7.1 登录流程 #
astro
---
// src/pages/login.astro
import { createSession } from '../lib/session';
const error = Astro.url.searchParams.get('error');
if (Astro.request.method === 'POST') {
const formData = await Astro.request.formData();
const email = formData.get('email') as string;
const password = formData.get('password') as string;
try {
const response = await fetch(`${import.meta.env.API_URL}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('登录失败');
}
const { user } = await response.json();
const sessionId = createSession(user.id);
Astro.cookies.set('sessionId', sessionId, {
path: '/',
httpOnly: true,
secure: true,
sameSite: 'strict',
});
return Astro.redirect('/dashboard');
} catch (e) {
return Astro.redirect('/login?error=1');
}
}
---
<div class="login-form">
{error && <p class="error">登录失败,请重试</p>}
<form method="POST">
<div>
<label for="email">邮箱</label>
<input type="email" name="email" required />
</div>
<div>
<label for="password">密码</label>
<input type="password" name="password" required />
</div>
<button type="submit">登录</button>
</form>
</div>
7.2 受保护页面 #
astro
---
// src/pages/dashboard.astro
import { getSession } from '../lib/session';
const sessionId = Astro.cookies.get('sessionId')?.value;
if (!sessionId) {
return Astro.redirect('/login');
}
const session = getSession(sessionId);
if (!session) {
Astro.cookies.delete('sessionId');
return Astro.redirect('/login');
}
const user = await fetch(`${import.meta.env.API_URL}/users/${session.userId}`).then(r => r.json());
---
<div class="dashboard">
<h1>欢迎, {user.name}</h1>
<p>邮箱: {user.email}</p>
<form action="/logout" method="POST">
<button type="submit">退出登录</button>
</form>
</div>
7.3 登出处理 #
astro
---
// src/pages/logout.astro
import { deleteSession } from '../lib/session';
if (Astro.request.method === 'POST') {
const sessionId = Astro.cookies.get('sessionId')?.value;
if (sessionId) {
deleteSession(sessionId);
}
Astro.cookies.delete('sessionId', { path: '/' });
return Astro.redirect('/login');
}
return Astro.redirect('/');
---
八、表单处理 #
8.1 表单提交 #
astro
---
// src/pages/contact.astro
let success = false;
let error = '';
if (Astro.request.method === 'POST') {
const formData = await Astro.request.formData();
const data = {
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
};
try {
const response = await fetch(`${import.meta.env.API_URL}/contact`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (response.ok) {
success = true;
} else {
error = '提交失败,请重试';
}
} catch (e) {
error = '网络错误,请重试';
}
}
---
<div class="contact-form">
{success && (
<div class="success">提交成功!</div>
)}
{error && (
<div class="error">{error}</div>
)}
<form method="POST">
<div>
<label for="name">姓名</label>
<input type="text" name="name" required />
</div>
<div>
<label for="email">邮箱</label>
<input type="email" name="email" required />
</div>
<div>
<label for="message">留言</label>
<textarea name="message" rows="5" required></textarea>
</div>
<button type="submit">提交</button>
</form>
</div>
九、总结 #
动态数据核心要点:
text
┌─────────────────────────────────────────────────────┐
│ 动态数据要点 │
├─────────────────────────────────────────────────────┤
│ │
│ 🔄 SSR 模式 请求时获取数据 │
│ │
│ 🔀 混合渲染 静态 + 动态结合 │
│ │
│ 📦 缓存策略 内存缓存、响应缓存 │
│ │
│ 🍪 Cookie 存储用户状态 │
│ │
│ 🔐 认证 登录、登出、会话管理 │
│ │
│ 📝 表单 POST 请求处理 │
│ │
└─────────────────────────────────────────────────────┘
下一步,让我们学习 内容集合入门,掌握 Astro 的内容管理功能!
最后更新:2026-03-28