乐闻世界logo
搜索文章和话题

前端面试题手册

Astro 支持哪些渲染模式?静态生成(SSG)和服务端渲染(SSR)有什么区别?

Astro 支持多种渲染模式,可以根据不同的使用场景选择最适合的渲染策略。理解这些渲染模式对于构建高性能的 Astro 应用至关重要。主要渲染模式:静态生成(Static Generation - SSG):默认模式在构建时生成 HTML适合内容不经常变化的页面性能最佳,SEO 友好 // src/pages/index.astro --- const posts = await fetch('https://api.example.com/posts').then(r => r.json()); --- <h1>博客文章</h1> {posts.map(post => ( <article> <h2>{post.title}</h2> <p>{post.excerpt}</p> </article> ))}服务端渲染(Server-Side Rendering - SSR):每次请求时动态生成 HTML适合需要实时数据的页面需要配置适配器(Adapter) // astro.config.mjs import { defineConfig } from 'astro/config'; import vercel from '@astrojs/vercel/server'; export default defineConfig({ output: 'server', adapter: vercel(), }); // src/pages/dashboard.astro --- // 每次请求都会执行 const user = await getUserFromSession(Astro.request); const data = await fetchUserData(user.id); --- <h1>欢迎, {user.name}</h1> <p>你的数据: {data}</p>混合渲染(Hybrid Rendering):结合静态和动态渲染可以为不同页面指定不同的渲染模式提供最大的灵活性 // astro.config.mjs import { defineConfig } from 'astro/config'; import vercel from '@astrojs/vercel/server'; export default defineConfig({ output: 'hybrid', adapter: vercel(), }); // src/pages/static.astro --- // 静态页面 export const prerender = true; --- <h1>静态页面</h1> // src/pages/dynamic.astro --- // 动态页面 export const prerender = false; --- <h1>动态页面</h1>客户端渲染(Client-Side Rendering):使用 client:only 指令完全在浏览器中渲染适合需要大量客户端交互的组件 --- import InteractiveChart from '../components/InteractiveChart.jsx'; --- <InteractiveChart client:only="react" />预渲染(Prerender)配置:---// 控制单个页面的预渲染行为export const prerender = true; // 静态生成export const prerender = false; // 服务端渲染export const prerender = 'auto'; // 自动判断---动态路由:---// src/pages/blog/[id].astroexport async function getStaticPaths() { const posts = await fetch('https://api.example.com/posts').then(r => r.json()); return posts.map(post => ({ params: { id: post.id }, props: { post }, }));}const { id } = Astro.params;const { post } = Astro.props;---<h1>{post.title}</h1><p>{post.content}</p>适配器(Adapters):适配器将 Astro 项目部署到不同的平台:# Vercelnpx astro add vercel# Netlifynpx astro add netlify# Cloudflarenpx astro add cloudflare# Node.jsnpx astro add node// astro.config.mjsimport { defineConfig } from 'astro/config';import vercel from '@astrojs/vercel/server';import netlify from '@astrojs/netlify/edge';import cloudflare from '@astrojs/cloudflare';export default defineConfig({ output: 'server', adapter: vercel(), // 或 netlify(), cloudflare()});选择渲染模式的指南:使用静态生成(SSG):博客文章文档页面营销页面内容不经常变化的页面使用服务端渲染(SSR):用户仪表板需要认证的页面实时数据展示个性化内容使用混合渲染:大部分内容静态,部分动态需要灵活性的大型应用既有公开页面又有私有页面的应用使用客户端渲染:复杂的交互式组件需要大量客户端状态管理不需要 SEO 的功能性能优化技巧:默认使用静态生成只为需要动态内容的页面启用 SSR使用 client:* 指令控制水合时机利用混合渲染平衡性能和功能合理使用适配器优化部署理解 Astro 的渲染模式可以帮助你构建既快速又灵活的 Web 应用。
阅读 0·2月21日 16:15

如何在 Astro 项目中集成和使用多个前端框架(React、Vue、Svelte)?

Astro 支持在同一个项目中混合使用多个前端框架,这是其最强大的特性之一。你可以在一个 Astro 项目中同时使用 React、Vue、Svelte、Preact、SolidJS 等框架。集成步骤:安装框架集成包: # 安装 React 集成 npx astro add react # 安装 Vue 集成 npx astro add vue # 安装 Svelte 集成 npx astro add svelte配置 astro.config.mjs: import { defineConfig } from 'astro/config'; import react from '@astrojs/react'; import vue from '@astrojs/vue'; import svelte from '@astrojs/svelte'; export default defineConfig({ integrations: [react(), vue(), svelte()], });使用不同框架的组件:---import ReactComponent from '../components/ReactComponent.jsx';import VueComponent from '../components/VueComponent.vue';import SvelteComponent from '../components/SvelteComponent.svelte';import AstroComponent from '../components/AstroComponent.astro';---<div> <AstroComponent /> <ReactComponent /> <VueComponent /> <SvelteComponent /></div>框架特定配置:React 配置:import { defineConfig } from 'astro/config';import react from '@astrojs/react';export default defineConfig({ integrations: [ react({ // React 特定配置 experimentalDirectRender: false, }), ],});Vue 配置:import { defineConfig } from 'astro/config';import vue from '@astrojs/vue';export default defineConfig({ integrations: [ vue({ // Vue 特定配置 template: { compilerOptions: { isCustomElement: (tag) => tag.includes('-'), }, }, }), ],});Svelte 配置:import { defineConfig } from 'astro/config';import svelte from '@astrojs/svelte';export default defineConfig({ integrations: [ svelte({ // Svelte 特定配置 preprocess: [], }), ],});混合框架的最佳实践:按需选择框架:React:适合复杂的交互式 UIVue:适合渐进式增强和快速开发Svelte:适合高性能组件Astro:适合静态内容和布局保持一致性:在同一功能模块中使用相同框架避免在单个组件中混合多个框架建立清晰的组件命名约定性能优化:只为需要交互的组件使用框架使用 client:* 指令控制水合时机考虑使用轻量级框架(如 Preact)替代 React示例:博客页面---import Layout from '../layouts/Layout.astro';import Header from '../components/Header.astro';import PostList from '../components/PostList.astro';import CommentSection from '../components/CommentSection.jsx'; // Reactimport LikeButton from '../components/LikeButton.vue'; // Vueimport ShareWidget from '../components/ShareWidget.svelte'; // Svelte---<Layout title="博客文章"> <Header /> <main> <PostList /> <CommentSection client:visible /> <LikeButton client:idle /> <ShareWidget client:idle /> </main></Layout>框架间数据共享:虽然不同框架的组件不能直接共享状态,但可以通过以下方式共享数据:通过 Props 传递: --- const data = await fetch('/api/data').then(r => r.json()); --- <ReactComponent data={data} /> <VueComponent :data={data} />使用全局状态管理:通过 API 获取数据使用 localStorage 或 sessionStorage使用自定义事件系统注意事项:每个框架的组件需要使用对应的文件扩展名(.jsx、.vue、.svelte)不同框架的组件不能直接相互引用确保所有框架的依赖都已正确安装某些框架可能需要额外的配置(如 Vue 的插件、React 的 Context)Astro 的多框架支持让你能够根据项目需求选择最合适的工具,同时保持高性能和优秀的开发体验。
阅读 0·2月21日 16:15

Astro 的图片优化功能是如何工作的?如何使用 `<Image>` 组件优化图片加载?

Astro 提供了强大的图片优化功能,可以自动处理图片的响应式、格式转换、压缩等任务,显著提升网站性能。核心功能:自动响应式图片:自动生成多个尺寸的图片格式转换:自动转换为现代图片格式(WebP、AVIF)懒加载:自动实现图片懒加载压缩优化:自动压缩图片减少文件大小基本用法:---import { Image } from 'astro:assets';import myImage from '../assets/my-image.jpg';---&lt;!-- 基本使用 --&gt;&lt;Image src={myImage} alt="描述文字" /&gt;&lt;!-- 指定宽度和高度 --&gt;&lt;Image src={myImage} alt="描述文字" width={800} height={600} /&gt;&lt;!-- 指定格式 --&gt;&lt;Image src={myImage} alt="描述文字" format="webp" /&gt;&lt;!-- 指定质量 --&gt;&lt;Image src={myImage} alt="描述文字" quality={80} /&gt;高级配置:---import { Image } from 'astro:assets';import heroImage from '../assets/hero.jpg';---&lt;Image src={heroImage} alt="Hero Image" widths={[400, 800, 1200]} sizes="(max-width: 768px) 100vw, 50vw" formats={['avif', 'webp', 'jpeg']} loading="lazy" decoding="async" priority={false}/&gt;配置选项说明:widths:生成多个宽度的图片版本sizes:指定不同屏幕宽度下的图片显示尺寸formats:指定输出格式(按优先级)loading:加载策略("eager" 或 "lazy")decoding:解码策略("sync" 或 "async")priority:是否为优先加载的图片远程图片:---import { Image } from 'astro:assets';import { getImage } from 'astro:assets';// 处理远程图片const remoteImage = await getImage({ src: 'https://example.com/image.jpg', alt: 'Remote Image', width: 800, height: 600, format: 'webp',});---&lt;img {...remoteImage.attributes} /&gt;配置远程图片域名:// astro.config.mjsimport { defineConfig } from 'astro/config';import react from '@astrojs/react';export default defineConfig({ integrations: [react()], image: { // 允许的远程图片域名 remotePatterns: [ { protocol: 'https', hostname: 'example.com', }, { protocol: 'https', hostname: '**.cdn.example.com', }, ], },});图片服务(Image Service):Astro 支持多种图片服务:// astro.config.mjsimport { defineConfig } from 'astro/config';import sharp from 'astro/assets/services/sharp';import imagetools from 'astro/assets/services/imagetools';export default defineConfig({ image: { // 使用 Sharp(默认,性能最佳) service: sharp, // 或使用 ImageTools(更多功能) // service: imagetools, },});背景图片:---import { Image } from 'astro:assets';import backgroundImage from '../assets/bg.jpg';const { src: bgSrc } = await getImage({ src: backgroundImage, format: 'webp', width: 1920,});---&lt;div style={`background-image: url('${bgSrc}');`} class="hero-section"&gt; &lt;h1&gt;Hero Section&lt;/h1&gt;&lt;/div&gt;图片画廊示例:---import { Image } from 'astro:assets';import { getCollection } from 'astro:content';const gallery = await getCollection('gallery');---&lt;div class="gallery"&gt; {gallery.map(item =&gt; ( &lt;figure&gt; &lt;Image src={item.data.image} alt={item.data.title} widths={[300, 600, 900]} sizes="(max-width: 600px) 100vw, 50vw" loading="lazy" /&gt; &lt;figcaption&gt;{item.data.title}&lt;/figcaption&gt; &lt;/figure&gt; ))}&lt;/div&gt;&lt;style&gt; .gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1rem; } figure { margin: 0; } img { width: 100%; height: auto; }&lt;/style&gt;性能优化技巧:使用正确的格式优先级: &lt;Image src={image} formats={['avif', 'webp', 'jpeg']} /&gt;合理的尺寸设置: &lt;Image src={image} widths={[400, 800, 1200, 1600]} sizes="(max-width: 768px) 100vw, 50vw" /&gt;优先加载关键图片: &lt;Image src={heroImage} loading="eager" priority={true} /&gt;懒加载非关键图片: &lt;Image src={image} loading="lazy" decoding="async" /&gt;控制图片质量: &lt;Image src={image} quality={75} /&gt;与内容集合集成:---title: "我的文章"image: ./hero.jpg---文章内容...---import { Image } from 'astro:assets';import { getEntry } from 'astro:content';const post = await getEntry('blog', 'my-post');const { image } = post.data;---&lt;Image src={image} alt={post.data.title} widths={[800, 1200, 1600]} /&gt;最佳实践:默认使用 &lt;Image&gt; 组件而不是 &lt;img&gt; 标签为所有图片提供有意义的 alt 文本根据图片用途设置合适的加载策略使用 widths 和 sizes 实现真正的响应式优先使用现代图片格式(AVIF、WebP)为远程图片配置域名白名单在构建时处理图片,避免运行时开销Astro 的图片优化功能可以显著提升网站性能,改善用户体验,同时保持简单的开发体验。
阅读 0·2月21日 16:15

Astro 有哪些性能优化策略?如何构建超快速的 Astro 网站?

Astro 提供了多种性能优化策略和技术,帮助开发者构建超快速的网站。了解这些优化技巧对于构建高性能的 Astro 应用至关重要。核心性能优化策略:零 JavaScript 默认:Astro 默认只输出纯 HTML只在需要时才加载 JavaScript显著减少初始加载时间岛屿架构优化:只为交互式组件添加 client:* 指令使用合适的 client:* 指令类型延迟非关键交互的水合代码分割和懒加载:---// 延迟加载组件import { lazy } from 'astro';const HeavyComponent = lazy(() =&gt; import('../components/HeavyComponent.jsx'));---&lt;HeavyComponent client:visible /&gt;图片优化:---import { Image } from 'astro:assets';import heroImage from '../assets/hero.jpg';---&lt;!-- 使用正确的格式和尺寸 --&gt;&lt;Image src={heroImage} alt="Hero" widths={[400, 800, 1200, 1600]} sizes="(max-width: 768px) 100vw, 50vw" formats={['avif', 'webp', 'jpeg']} loading="eager" priority={true}/&gt;&lt;!-- 懒加载非关键图片 --&gt;&lt;Image src={image} alt="Gallery Image" loading="lazy" decoding="async"/&gt;CSS 优化:---// 使用作用域样式---&lt;style&gt; /* 作用域样式,不会影响其他组件 */ .container { max-width: 1200px; margin: 0 auto; }&lt;/style&gt;&lt;style is:global&gt; /* 全局样式,谨慎使用 */ body { font-family: system-ui, sans-serif; }&lt;/style&gt;预加载关键资源:---// src/layouts/Layout.astro---&lt;html&gt; &lt;head&gt; &lt;!-- 预加载关键 CSS --&gt; &lt;link rel="preload" href="/styles/critical.css" as="style" /&gt; &lt;!-- 预加载字体 --&gt; &lt;link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin /&gt; &lt;!-- 预连接到外部域名 --&gt; &lt;link rel="preconnect" href="https://api.example.com" /&gt; &lt;!-- DNS 预解析 --&gt; &lt;link rel="dns-prefetch" href="https://cdn.example.com" /&gt; &lt;/head&gt; &lt;body&gt; &lt;slot /&gt; &lt;/body&gt;&lt;/html&gt;数据获取优化:---// src/pages/blog/[slug].astroimport { getEntry } from 'astro:content';// 并行获取数据const [post, relatedPosts, comments] = await Promise.all([ getEntry('blog', Astro.params.slug), fetchRelatedPosts(Astro.params.slug), fetchComments(Astro.params.slug),]);// 使用缓存const cachedData = await cache.get(`post:${Astro.params.slug}`);if (cachedData) { return cachedData;}const data = await fetchData();await cache.set(`post:${Astro.params.slug}`, data, { ttl: 3600 });---&lt;h1&gt;{post.data.title}&lt;/h1&gt;&lt;Content /&gt;构建优化:// astro.config.mjsimport { defineConfig } from 'astro/config';export default defineConfig({ build: { // 优化构建输出 inlineStylesheets: 'auto', }, vite: { build: { // 代码分割 rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], utils: ['lodash', 'date-fns'], }, }, }, }, },});服务端渲染优化:// src/middleware.tsexport const onRequest = async (context, next) =&gt; { // 添加缓存头 const response = await next(); // 为静态资源添加长期缓存 if (context.url.pathname.match(/\.(js|css|png|jpg|jpeg|gif|webp|svg)$/)) { response.headers.set('Cache-Control', 'public, max-age=31536000, immutable'); } // 为 API 响应添加短期缓存 if (context.url.pathname.startsWith('/api/')) { response.headers.set('Cache-Control', 'public, max-age=60'); } return response;};使用适配器优化部署:// astro.config.mjsimport { defineConfig } from 'astro/config';import vercel from '@astrojs/vercel/server';export default defineConfig({ output: 'server', adapter: vercel({ // Vercel 特定优化 imageService: true, edgeMiddleware: true, }),});性能监控和分析:// src/lib/performance.tsexport function measurePerformance(name: string, fn: () =&gt; Promise&lt;void&gt;) { return async () =&gt; { const start = performance.now(); await fn(); const duration = performance.now() - start; console.log(`${name} took ${duration.toFixed(2)}ms`); };}// 使用export async function GET(context) { await measurePerformance('data-fetch', async () =&gt; { const data = await fetchData(); return new Response(JSON.stringify(data)); });}Web Vitals 优化:---// src/layouts/Layout.astro---&lt;html&gt; &lt;head&gt; &lt;script&gt; // 监控 Core Web Vitals import { onCLS, onFID, onFCP, onLCP, onTTFB } from 'web-vitals'; onCLS(console.log); onFID(console.log); onFCP(console.log); onLCP(console.log); onTTFB(console.log); &lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;slot /&gt; &lt;/body&gt;&lt;/html&gt;减少第三方脚本:---// 延迟加载分析脚本---&lt;script&gt; // 只在生产环境加载 if (import.meta.env.PROD) { window.addEventListener('load', () =&gt; { const script = document.createElement('script'); script.src = 'https://analytics.example.com/script.js'; script.async = true; document.head.appendChild(script); }); }&lt;/script&gt;使用 Service Worker 缓存:// public/sw.jsconst CACHE_NAME = 'astro-v1';const urlsToCache = ['/', '/styles/main.css'];self.addEventListener('install', (event) =&gt; { event.waitUntil( caches.open(CACHE_NAME).then((cache) =&gt; cache.addAll(urlsToCache)) );});self.addEventListener('fetch', (event) =&gt; { event.respondWith( caches.match(event.request).then((response) =&gt; response || fetch(event.request)) );});---// 注册 Service Worker---&lt;script&gt; if ('serviceWorker' in navigator) { window.addEventListener('load', () =&gt; { navigator.serviceWorker.register('/sw.js'); }); }&lt;/script&gt;性能优化清单:构建时优化:启用代码分割压缩和优化资源使用 Tree Shaking优化图片和字体运行时优化:使用合适的 client:* 指令实现懒加载优化数据获取使用缓存策略网络优化:使用 CDN启用压缩(gzip、brotli)优化 HTTP 请求使用 HTTP/2 或 HTTP/3渲染优化:减少重绘和回流使用 CSS 动画而非 JavaScript优化 DOM 结构避免强制同步布局性能测试工具:LighthouseWebPageTestChrome DevTools PerformanceAstro 内置的构建分析Astro 的性能优化策略可以帮助你构建超快速的网站,提供优秀的用户体验和 SEO 表现。
阅读 0·2月21日 16:15

Astro 的中间件(Middleware)是如何工作的?有哪些常见的使用场景?

Astro 的中间件(Middleware)是一个强大的功能,允许你在请求到达页面之前拦截和处理请求,实现认证、重定向、修改响应等功能。核心概念:中间件在服务器端运行,可以访问请求对象,并在请求处理链中执行自定义逻辑。创建中间件:// src/middleware.tsimport { defineMiddleware } from 'astro:middleware';import type { MiddlewareResponseHandler } from 'astro';export const onRequest: MiddlewareResponseHandler = async (context, next) =&gt; { // 在这里处理请求 // 调用 next() 继续处理链 const response = await next(); // 可以修改响应 response.headers.set('X-Custom-Header', 'Custom Value'); return response;};中间件上下文:// src/middleware.tsexport const onRequest = async (context, next) =&gt; { // context 包含以下属性: console.log(context.request); // Request 对象 console.log(context.url); // URL 对象 console.log(context.cookies); // Cookies console.log(context.locals); // 本地数据存储 console.log(context.site); // 站点配置 const response = await next(); return response;};使用场景:认证和授权:// src/middleware.tsexport const onRequest = async (context, next) =&gt; { const protectedRoutes = ['/dashboard', '/settings', '/profile']; const currentPath = context.url.pathname; if (protectedRoutes.some(route =&gt; currentPath.startsWith(route))) { const token = context.cookies.get('auth-token'); if (!token) { return context.redirect('/login'); } // 验证 token const user = await verifyToken(token.value); if (!user) { return context.redirect('/login'); } // 将用户信息存储在 locals 中 context.locals.user = user; } return next();};重定向管理:// src/middleware.tsexport const onRequest = async (context, next) =&gt; { const redirects = { '/old-url': '/new-url', '/old-page': '/new-page', }; const currentPath = context.url.pathname; if (redirects[currentPath]) { return context.redirect(redirects[currentPath], 301); } return next();};国际化(i18n):// src/middleware.tsexport const onRequest = async (context, next) =&gt; { const supportedLocales = ['en', 'zh', 'ja']; const defaultLocale = 'en'; // 从 URL 获取语言 const pathSegments = context.url.pathname.split('/'); const locale = pathSegments[1]; // 检查是否是支持的语言 if (supportedLocales.includes(locale)) { context.locals.locale = locale; return next(); } // 从 Accept-Language 头获取语言 const acceptLanguage = context.request.headers.get('accept-language'); const browserLocale = acceptLanguage?.split(',')[0].split('-')[0] || defaultLocale; const targetLocale = supportedLocales.includes(browserLocale) ? browserLocale : defaultLocale; return context.redirect(`/${targetLocale}${context.url.pathname}`);};日志记录:// src/middleware.tsexport const onRequest = async (context, next) =&gt; { const startTime = Date.now(); const response = await next(); const duration = Date.now() - startTime; console.log(`${context.request.method} ${context.url.pathname} - ${duration}ms`); return response;};CORS 处理:// src/middleware.tsexport const onRequest = async (context, next) =&gt; { const response = await next(); response.headers.set('Access-Control-Allow-Origin', '*'); response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization'); return response;};条件中间件:// src/middleware.tsexport const onRequest = async (context, next) =&gt; { // 只对特定路径应用中间件 if (context.url.pathname.startsWith('/api/')) { console.log('API request:', context.url.pathname); } return next();};中间件优先级:Astro 支持在不同层级定义中间件:全局中间件:src/middleware.ts - 应用于所有请求路由中间件:在特定路由目录中定义 - 应用于该路由及其子路由// src/middleware.ts (全局)export const onRequest = async (context, next) =&gt; { console.log('Global middleware'); return next();};// src/pages/dashboard/middleware.ts (路由特定)export const onRequest = async (context, next) =&gt; { console.log('Dashboard middleware'); return next();};使用 locals 传递数据:// src/middleware.tsexport const onRequest = async (context, next) =&gt; { // 在中间件中设置数据 context.locals.user = await getUser(context); context.locals.theme = 'dark'; const response = await next(); return response;};---// 在页面中使用 localsconst user = Astro.locals.user;const theme = Astro.locals.theme;---&lt;h1&gt;欢迎, {user?.name}&lt;/h1&gt;&lt;p&gt;当前主题: {theme}&lt;/p&gt;错误处理:// src/middleware.tsexport const onRequest = async (context, next) =&gt; { try { return await next(); } catch (error) { console.error('Middleware error:', error); return new Response('Internal Server Error', { status: 500, headers: { 'Content-Type': 'text/plain' }, }); }};性能优化:// src/middleware.tsexport const onRequest = async (context, next) =&gt; { // 缓存频繁访问的数据 const cacheKey = `user:${context.cookies.get('user-id')?.value}`; const cachedUser = await cache.get(cacheKey); if (cachedUser) { context.locals.user = cachedUser; return next(); } const user = await fetchUser(context.cookies.get('user-id')?.value); await cache.set(cacheKey, user, { ttl: 3600 }); context.locals.user = user; return next();};最佳实践:保持中间件简洁高效避免在中间件中执行耗时操作使用缓存优化性能合理使用 locals 传递数据实现适当的错误处理考虑中间件的执行顺序只在必要时使用重定向Astro 的中间件功能为构建复杂的应用提供了强大的请求处理能力,特别适合需要认证、授权、国际化等功能的项目。
阅读 0·2月21日 16:15

Astro 的岛屿架构(Islands Architecture)是如何工作的?client 指令有哪些类型?

Astro 的岛屿架构(Islands Architecture)是一种创新的 Web 开发模式,它解决了传统单页应用(SPA)的性能问题。核心概念:岛屿架构将页面视为静态 HTML 的海洋,其中散布着交互式的 JavaScript"岛屿"。默认情况下,Astro 组件是静态的,只输出 HTML。只有当你明确使用 client:* 指令时,组件才会变成交互式的岛屿。client 指令类型:client:load:页面加载时立即水合组件 &lt;InteractiveComponent client:load /&gt;client:idle:浏览器空闲时水合组件(推荐用于非关键交互) &lt;InteractiveComponent client:idle /&gt;client:visible:组件进入视口时才水合(适合滚动加载) &lt;LazyComponent client:visible /&gt;client:media:匹配特定媒体查询时才水合 &lt;ResponsiveComponent client:media="(max-width: 768px)" /&gt;client:only:只在客户端渲染,不进行服务端渲染 &lt;ClientOnlyComponent client:only="react" /&gt;性能优势:减少 JavaScript 包体积:只有交互式组件的 JavaScript 才会被发送到浏览器更快的首次内容绘制(FCP):静态 HTML 可以立即显示更好的 SEO:搜索引擎可以直接索引静态内容渐进式增强:核心内容立即可用,交互功能按需加载实际应用示例:---import Header from '../components/Header.astro';import BlogPost from '../components/BlogPost.astro';import CommentSection from '../components/CommentSection.jsx';import NewsletterForm from '../components/NewsletterForm.svelte';---&lt;Header /&gt;&lt;main&gt; &lt;BlogPost /&gt; &lt;CommentSection client:visible /&gt; &lt;NewsletterForm client:idle /&gt;&lt;/main&gt;在这个例子中:Header 和 BlogPost 是静态的(无 JavaScript)CommentSection 在滚动到视口时才加载 JavaScriptNewsletterForm 在浏览器空闲时才加载与传统 SPA 的对比:传统 SPA 需要下载整个应用的 JavaScript 并进行水合,而岛屿架构只下载和执行必要的交互部分。这使得 Astro 网站通常比同等的 SPA 快 2-3 倍。最佳实践:默认不使用 client:* 指令只为真正需要交互的组件添加 client:*根据交互的紧急程度选择合适的指令使用 client:visible 处理滚动加载的组件使用 client:idle 处理非关键交互功能岛屿架构让开发者能够构建既快速又具有丰富交互体验的网站。
阅读 0·2月21日 16:15

如何在 Astro 中实现国际化(i18n)?如何配置多语言网站?

Astro 的国际化(i18n)功能让开发者能够轻松构建多语言网站。了解如何配置和使用 Astro 的 i18n 功能对于面向全球用户的项目至关重要。基本配置:// astro.config.mjsimport { defineConfig } from 'astro/config';import { i18n } from 'astro-i18next';export default defineConfig({ integrations: [ i18n({ defaultLocale: 'en', locales: ['en', 'zh', 'ja', 'es'], fallbackLocale: 'en', routing: { prefixDefaultLocale: false, }, }), ],});使用 astro-i18next 集成:npm install astro-i18next i18next// astro.config.mjsimport { defineConfig } from 'astro/config';import i18next from 'astro-i18next';export default defineConfig({ integrations: [ i18next({ defaultLocale: 'en', locales: ['en', 'zh'], routingStrategy: 'prefix', }), ],});翻译文件结构:src/├── i18n/│ ├── en/│ │ ├── common.json│ │ └── home.json│ ├── zh/│ │ ├── common.json│ │ └── home.json│ └── ja/│ ├── common.json│ └── home.json翻译文件示例:// src/i18n/en/common.json{ "nav": { "home": "Home", "about": "About", "contact": "Contact" }, "buttons": { "submit": "Submit", "cancel": "Cancel" }}// src/i18n/zh/common.json{ "nav": { "home": "首页", "about": "关于", "contact": "联系" }, "buttons": { "submit": "提交", "cancel": "取消" }}在组件中使用翻译:---import { useTranslation } from 'astro-i18next';const { t } = useTranslation();---&lt;nav&gt; &lt;a href="/"&gt;{t('nav.home')}&lt;/a&gt; &lt;a href="/about"&gt;{t('nav.about')}&lt;/a&gt; &lt;a href="/contact"&gt;{t('nav.contact')}&lt;/a&gt;&lt;/nav&gt;&lt;button&gt;{t('buttons.submit')}&lt;/button&gt;语言切换器:---import { useTranslation, useLocale } from 'astro-i18next';const { t } = useTranslation();const locale = useLocale();const locales = ['en', 'zh', 'ja', 'es'];---&lt;div class="language-switcher"&gt; {locales.map(loc =&gt; ( &lt;a href={`/${loc === 'en' ? '' : loc}`} class={locale === loc ? 'active' : ''} &gt; {loc.toUpperCase()} &lt;/a&gt; ))}&lt;/div&gt;&lt;style&gt; .language-switcher { display: flex; gap: 0.5rem; } .active { font-weight: bold; text-decoration: underline; }&lt;/style&gt;动态路由国际化:---// src/pages/[lang]/blog/[slug].astroimport { useTranslation, useLocale } from 'astro-i18next';import { getCollection } from 'astro:content';const { t } = useTranslation();const locale = useLocale();const { slug } = Astro.params;const post = await getEntry('blog', slug);const { Content } = await post.render();---&lt;h1&gt;{post.data.title}&lt;/h1&gt;&lt;Content /&gt;内容集合国际化:---# src/content/en/blog/my-post.mdtitle: "My English Post"publishDate: 2024-01-15---This is the English version of the post.---# src/content/zh/blog/my-post.mdtitle: "我的中文文章"publishDate: 2024-01-15---这是文章的中文版本。---// src/pages/blog/[slug].astroimport { getCollection } from 'astro:content';import { useLocale } from 'astro-i18next';const locale = useLocale();const { slug } = Astro.params;const post = await getEntry(`blog-${locale}`, slug);const { Content } = await post.render();---&lt;h1&gt;{post.data.title}&lt;/h1&gt;&lt;Content /&gt;日期和数字格式化:---import { useTranslation } from 'astro-i18next';const { t, i18n } = useTranslation();const date = new Date();const number = 1234567.89;---&lt;p&gt;Date: {date.toLocaleDateString(i18n.language)}&lt;/p&gt;&lt;p&gt;Number: {number.toLocaleString(i18n.language)}&lt;/p&gt;&lt;p&gt;Currency: {number.toLocaleString(i18n.language, { style: 'currency', currency: 'USD' })}&lt;/p&gt;SEO 优化:---import { useTranslation, useLocale } from 'astro-i18next';const { t } = useTranslation();const locale = useLocale();---&lt;html lang={locale}&gt; &lt;head&gt; &lt;meta charset="UTF-8" /&gt; &lt;title&gt;{t('meta.title')}&lt;/title&gt; &lt;meta name="description" content={t('meta.description')} /&gt; &lt;!-- 语言切换链接 --&gt; &lt;link rel="alternate" hreflang="en" href="/en" /&gt; &lt;link rel="alternate" hreflang="zh" href="/zh" /&gt; &lt;link rel="alternate" hreflang="ja" href="/ja" /&gt; &lt;link rel="alternate" hreflang="x-default" href="/" /&gt; &lt;/head&gt; &lt;body&gt; &lt;slot /&gt; &lt;/body&gt;&lt;/html&gt;服务端渲染(SSR)国际化:// src/middleware.tsimport { defineMiddleware } from 'astro:middleware';export const onRequest = defineMiddleware((context, next) =&gt; { const url = new URL(context.request.url); const pathSegments = url.pathname.split('/').filter(Boolean); // 检测语言 const detectedLocale = detectLocale(context.request); // 如果没有语言前缀,重定向到检测到的语言 if (!pathSegments[0] || !['en', 'zh', 'ja'].includes(pathSegments[0])) { return context.redirect(`/${detectedLocale}${url.pathname}`); } // 存储语言到 locals context.locals.locale = pathSegments[0]; return next();});function detectLocale(request: Request): string { const acceptLanguage = request.headers.get('accept-language'); const browserLocale = acceptLanguage?.split(',')[0].split('-')[0] || 'en'; const supportedLocales = ['en', 'zh', 'ja']; return supportedLocales.includes(browserLocale) ? browserLocale : 'en';}RTL(从右到左)语言支持:---import { useTranslation, useLocale } from 'astro-i18next';const { t } = useTranslation();const locale = useLocale();const rtlLocales = ['ar', 'he', 'fa'];const isRTL = rtlLocales.includes(locale);---&lt;html lang={locale} dir={isRTL ? 'rtl' : 'ltr'}&gt; &lt;head&gt; &lt;style&gt; body { direction: {isRTL ? 'rtl' : 'ltr'}; } &lt;/style&gt; &lt;/head&gt; &lt;body&gt; &lt;slot /&gt; &lt;/body&gt;&lt;/html&gt;最佳实践:翻译管理:使用专业的翻译工具(如 Crowdin、Locize)保持翻译文件结构一致定期审查和更新翻译性能优化:按需加载翻译文件使用缓存减少重复请求预加载常用语言用户体验:提供清晰的语言切换器记住用户语言偏好处理缺失的翻译SEO 考虑:为每种语言设置正确的 hreflang使用适当的语言标签避免重复内容问题开发流程:使用类型安全的翻译键自动化翻译检查集成到 CI/CD 流程Astro 的国际化功能提供了灵活的多语言支持,帮助开发者构建面向全球用户的应用。
阅读 0·2月21日 16:14

如何部署 Astro 应用到不同的平台(Vercel、Netlify、Node.js)?有哪些部署最佳实践?

Astro 的部署方式取决于你选择的渲染模式(SSG、SSR 或混合模式)。了解不同的部署选项和最佳实践对于成功发布 Astro 项目至关重要。静态部署(SSG):对于纯静态站点,可以将构建输出部署到任何静态托管服务。Vercel 部署: # 安装 Vercel CLI npm i -g vercel # 部署 vercel // vercel.json { "buildCommand": "astro build", "outputDirectory": "dist" }Netlify 部署: # 安装 Netlify CLI npm i -g netlify-cli # 部署 netlify deploy --prod # netlify.toml [build] command = "astro build" publish = "dist" [[redirects]] from = "/*" to = "/index.html" status = 200GitHub Pages 部署: // astro.config.mjs import { defineConfig } from 'astro/config'; export default defineConfig({ site: 'https://username.github.io', base: '/repository-name', }); # 构建并部署 npm run build # 将 dist 目录内容推送到 gh-pages 分支服务端部署(SSR):对于需要服务端渲染的应用,需要使用适配器。Vercel SSR 部署: npx astro add vercel // astro.config.mjs import { defineConfig } from 'astro/config'; import vercel from '@astrojs/vercel/server'; export default defineConfig({ output: 'server', adapter: vercel(), });Netlify Edge Functions: npx astro add netlify // astro.config.mjs import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify/edge'; export default defineConfig({ output: 'server', adapter: netlify(), });Node.js 服务器: npx astro add node // astro.config.mjs import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone', }), }); # 构建并运行 npm run build node ./dist/server/entry.mjsCloudflare Pages: npx astro add cloudflare // astro.config.mjs import { defineConfig } from 'astro/config'; import cloudflare from '@astrojs/cloudflare'; export default defineConfig({ output: 'server', adapter: cloudflare(), });Docker 部署:# DockerfileFROM node:18-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run buildFROM node:18-alpine AS runnerWORKDIR /appCOPY --from=builder /app/package*.json ./RUN npm ci --productionCOPY --from=builder /app/dist ./distEXPOSE 4321CMD ["node", "./dist/server/entry.mjs"]# 构建和运行 Docker 镜像docker build -t astro-app .docker run -p 4321:4321 astro-app环境变量配置:# .env.examplePUBLIC_API_URL=https://api.example.comDATABASE_URL=postgresql://...SECRET_KEY=your-secret-key// astro.config.mjsimport { defineConfig } from 'astro/config';export default defineConfig({ vite: { define: { 'import.meta.env.PUBLIC_API_URL': JSON.stringify(process.env.PUBLIC_API_URL), }, },});CI/CD 配置:# .github/workflows/deploy.ymlname: Deployon: push: branches: [main]jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Build run: npm run build - name: Deploy to Vercel uses: amondnet/vercel-action@v20 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.ORG_ID }} vercel-project-id: ${{ secrets.PROJECT_ID }} vercel-args: '--prod'性能优化部署:启用压缩: // astro.config.mjs export default defineConfig({ compressHTML: true, });配置 CDN: // astro.config.mjs export default defineConfig({ build: { assets: '_astro', }, });缓存策略: // src/middleware.ts export const onRequest = async (context, next) =&gt; { const response = await next(); if (context.url.pathname.startsWith('/_astro/')) { response.headers.set('Cache-Control', 'public, max-age=31536000, immutable'); } return response; };监控和日志:// src/lib/monitoring.tsexport function logDeploymentInfo() { console.log({ version: import.meta.env.PUBLIC_VERSION, buildTime: new Date().toISOString(), environment: import.meta.env.MODE, });}// 在入口文件中调用logDeploymentInfo();最佳实践:选择合适的部署平台:静态站点:Vercel、Netlify、GitHub PagesSSR 应用:Vercel、Netlify Edge、Node.js边缘计算:Cloudflare Workers、Vercel Edge环境变量管理:使用 .env 文件进行本地开发在部署平台配置生产环境变量区分公开和私有变量自动化部署:使用 CI/CD 管道自动运行测试自动部署到生产环境监控和日志:设置错误追踪监控性能指标记录部署信息回滚策略:保留多个部署版本快速回滚到稳定版本使用功能标志Astro 提供了灵活的部署选项,可以根据项目需求选择最适合的部署策略。
阅读 0·2月21日 16:14

什么是 Astro 的内容集合(Content Collections)?如何使用它来管理博客文章或文档?

Astro 的内容集合(Content Collections)是一个强大的功能,用于管理结构化内容,如博客文章、文档、产品目录等。它提供了类型安全、性能优化和开发体验提升。核心概念:内容集合允许你在 src/content 目录下组织内容,并通过类型安全的 API 访问这些内容。设置内容集合:创建集合配置: // src/content/config.ts import { defineCollection, z } from 'astro:content'; const blog = defineCollection({ type: 'content', // 使用 Markdown/MDX schema: z.object({ title: z.string(), description: z.string(), publishDate: z.coerce.date(), tags: z.array(z.string()), image: z.string().optional(), }), }); const products = defineCollection({ type: 'data', // 使用 JSON/YAML schema: z.object({ name: z.string(), price: z.number(), category: z.string(), }), }); export const collections = { blog, products };创建内容文件: &lt;!-- src/content/blog/my-first-post.md --&gt; --- title: "我的第一篇文章" description: "这是文章描述" publishDate: 2024-01-15 tags: ["astro", "tutorial"] image: "/images/post-1.jpg" --- 这是文章的正文内容... // src/content/products/product-1.json { "name": "产品 1", "price": 99.99, "category": "电子" }查询内容集合:---import { getCollection } from 'astro:content';// 获取所有博客文章const allPosts = await getCollection('blog');// 按日期排序const posts = allPosts .filter(post =&gt; post.data.publishDate &lt;= new Date()) .sort((a, b) =&gt; b.data.publishDate.valueOf() - a.data.publishDate.valueOf());// 获取特定标签的文章const astroPosts = await getCollection('blog', ({ data }) =&gt; { return data.tags.includes('astro');});---&lt;h1&gt;博客文章&lt;/h1&gt;{posts.map(post =&gt; ( &lt;article&gt; &lt;h2&gt;{post.data.title}&lt;/h2&gt; &lt;p&gt;{post.data.description}&lt;/p&gt; &lt;time&gt;{post.data.publishDate.toLocaleDateString()}&lt;/time&gt; &lt;a href={`/blog/${post.slug}`}&gt;阅读更多&lt;/a&gt; &lt;/article&gt;))}获取单个条目:---import { getEntry } from 'astro:content';import type { CollectionEntry } from 'astro:content';// 通过 slug 获取单个条目const post = await getEntry('blog', 'my-first-post');// 或者从 params 获取const { slug } = Astro.params;const post = await getEntry('blog', slug);if (!post) { return Astro.redirect('/404');}const { Content } = await post.render();---&lt;h1&gt;{post.data.title}&lt;/h1&gt;&lt;p&gt;{post.data.description}&lt;/p&gt;&lt;Content /&gt;动态路由与内容集合:---// src/pages/blog/[slug].astroimport { getCollection } from 'astro:content';export async function getStaticPaths() { const posts = await getCollection('blog'); return posts.map(post =&gt; ({ params: { slug: post.slug }, props: { post }, }));}const { post } = Astro.props;const { Content } = await post.render();---&lt;h1&gt;{post.data.title}&lt;/h1&gt;&lt;Content /&gt;使用 MDX:---title: "使用 MDX 的文章"description: "支持 JSX 的 Markdown"publishDate: 2024-01-20tags: ["mdx", "astro"]---import { Counter } from '../../components/Counter.jsx';这是普通的 Markdown 内容。&lt;Counter /&gt;你可以在这里使用任何组件!内容集合的优势:类型安全:使用 Zod schema 定义内容结构TypeScript 自动推断类型编译时验证性能优化:构建时处理内容自动生成路由避免运行时解析开发体验:IDE 自动完成类型检查错误提示灵活性:支持 Markdown、MDX、JSON、YAML自定义 schema前置数据处理高级用法:嵌套目录: src/content/ ├── blog/ │ ├── 2024/ │ │ ├── january/ │ │ │ └── post.md │ │ └── february/ │ │ └── post.md │ └── 2023/ │ └── post.md └── docs/ └── guide.md自定义渲染器: // src/content/config.ts import { defineCollection, z } from 'astro:content'; const blog = defineCollection({ type: 'content', schema: z.object({ title: z.string(), }), // 自定义渲染器 render: async ({ entry }) =&gt; { // 自定义渲染逻辑 return entry.render(); }, });内容转换: // src/content/config.ts const blog = defineCollection({ type: 'content', schema: z.object({ title: z.string(), date: z.coerce.date(), }), transform: async (data, id) =&gt; { // 转换数据 return { ...data, slug: id.replace('.md', ''), }; }, });最佳实践:使用 Zod schema 定义清晰的内容结构为不同的内容类型创建单独的集合利用 TypeScript 获得类型安全使用 getStaticPaths 生成动态路由在构建时处理所有内容,避免运行时开销内容集合是 Astro 处理结构化内容的最佳方式,特别适合博客、文档、产品目录等内容驱动的网站。
阅读 0·2月21日 16:14

如何在 Astro 中创建和使用 API 路由?如何处理请求和响应?

Astro 的 API 路由功能允许你在 Astro 项目中创建服务器端 API 端点,用于处理数据请求、身份验证、数据库操作等。基本概念:API 路由位于 src/pages/api/ 目录下,每个文件对应一个 API 端点。创建 API 路由:// src/pages/api/hello.tsexport async function GET(context) { return new Response(JSON.stringify({ message: 'Hello, World!' }), { headers: { 'Content-Type': 'application/json', }, });}支持的 HTTP 方法:// src/pages/api/users.tsexport async function GET(context) { const users = await fetchUsers(); return new Response(JSON.stringify(users), { headers: { 'Content-Type': 'application/json' }, });}export async function POST(context) { const body = await context.request.json(); const newUser = await createUser(body); return new Response(JSON.stringify(newUser), { status: 201, headers: { 'Content-Type': 'application/json' }, });}export async function PUT(context) { const body = await context.request.json(); const updatedUser = await updateUser(body); return new Response(JSON.stringify(updatedUser), { headers: { 'Content-Type': 'application/json' }, });}export async function DELETE(context) { const { id } = context.params; await deleteUser(id); return new Response(null, { status: 204 });}动态路由参数:// src/pages/api/users/[id].tsexport async function GET(context) { const { id } = context.params; const user = await fetchUserById(id); if (!user) { return new Response(JSON.stringify({ error: 'User not found' }), { status: 404, headers: { 'Content-Type': 'application/json' }, }); } return new Response(JSON.stringify(user), { headers: { 'Content-Type': 'application/json' }, });}请求和响应处理:// src/pages/api/search.tsexport async function POST(context) { try { // 获取请求体 const body = await context.request.json(); const { query } = body; // 获取查询参数 const url = new URL(context.request.url); const limit = parseInt(url.searchParams.get('limit') || '10'); // 获取请求头 const authHeader = context.request.headers.get('Authorization'); // 获取 Cookie const sessionCookie = context.cookies.get('session'); // 处理业务逻辑 const results = await search(query, limit); // 返回响应 return new Response(JSON.stringify({ results }), { status: 200, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=3600', }, }); } catch (error) { return new Response(JSON.stringify({ error: 'Internal Server Error' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); }}身份验证示例:// src/pages/api/protected.tsexport async function GET(context) { const token = context.request.headers.get('Authorization'); if (!token) { return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' }, }); } const user = await verifyToken(token); if (!user) { return new Response(JSON.stringify({ error: 'Invalid token' }), { status: 403, headers: { 'Content-Type': 'application/json' }, }); } const data = await fetchProtectedData(user.id); return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' }, });}文件上传:// src/pages/api/upload.tsexport async function POST(context) { try { const formData = await context.request.formData(); const file = formData.get('file') as File; if (!file) { return new Response(JSON.stringify({ error: 'No file provided' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } // 验证文件类型 const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']; if (!allowedTypes.includes(file.type)) { return new Response(JSON.stringify({ error: 'Invalid file type' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } // 验证文件大小(最大 5MB) const maxSize = 5 * 1024 * 1024; if (file.size &gt; maxSize) { return new Response(JSON.stringify({ error: 'File too large' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } // 保存文件 const url = await uploadFile(file); return new Response(JSON.stringify({ url }), { status: 201, headers: { 'Content-Type': 'application/json' }, }); } catch (error) { return new Response(JSON.stringify({ error: 'Upload failed' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); }}使用数据库:// src/pages/api/posts.tsimport { db } from '../../lib/db';export async function GET(context) { const url = new URL(context.request.url); const page = parseInt(url.searchParams.get('page') || '1'); const limit = parseInt(url.searchParams.get('limit') || '10'); const offset = (page - 1) * limit; const posts = await db.post.findMany({ take: limit, skip: offset, orderBy: { createdAt: 'desc' }, }); const total = await db.post.count(); return new Response(JSON.stringify({ posts, pagination: { page, limit, total, totalPages: Math.ceil(total / limit), }, }), { headers: { 'Content-Type': 'application/json' }, });}export async function POST(context) { const body = await context.request.json(); const { title, content, authorId } = body; const post = await db.post.create({ data: { title, content, authorId, }, }); return new Response(JSON.stringify(post), { status: 201, headers: { 'Content-Type': 'application/json' }, });}错误处理:// src/lib/api-error.tsexport class ApiError extends Error { constructor( public statusCode: number, message: string, public code?: string ) { super(message); this.name = 'ApiError'; }}export function handleApiError(error: unknown) { if (error instanceof ApiError) { return new Response( JSON.stringify({ error: error.message, code: error.code, }), { status: error.statusCode, headers: { 'Content-Type': 'application/json' }, } ); } console.error('Unexpected error:', error); return new Response( JSON.stringify({ error: 'Internal Server Error' }), { status: 500, headers: { 'Content-Type': 'application/json' }, } );}// src/pages/api/data.tsimport { ApiError, handleApiError } from '../../lib/api-error';export async function GET(context) { try { const data = await fetchData(); if (!data) { throw new ApiError(404, 'Data not found', 'NOT_FOUND'); } return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' }, }); } catch (error) { return handleApiError(error); }}CORS 配置:// src/pages/api/cors-example.tsexport async function OPTIONS(context) { return new Response(null, { status: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Max-Age': '86400', }, });}export async function GET(context) { const data = await fetchData(); return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, });}最佳实践:使用适当的 HTTP 方法(GET、POST、PUT、DELETE)实现适当的错误处理和状态码验证和清理输入数据使用 TypeScript 获得类型安全实现身份验证和授权添加适当的日志记录使用环境变量管理敏感信息实现速率限制防止滥用使用适当的缓存策略编写测试确保 API 可靠性Astro 的 API 路由功能为构建全栈应用提供了强大的服务器端能力,同时保持了 Astro 的简洁和性能优势。
阅读 0·2月21日 16:14