布局组件 #
一、什么是布局组件? #
布局组件是一种特殊的 Astro 组件,用于定义页面的公共结构和样式。它通常包含:
- HTML 文档结构
- 公共头部(导航、样式)
- 公共底部(页脚、脚本)
- 插槽用于插入页面内容
布局的作用 #
text
┌─────────────────────────────────────────────────────┐
│ 布局组件作用 │
├─────────────────────────────────────────────────────┤
│ │
│ 🎯 统一结构 - 所有页面共享相同的 HTML 结构 │
│ │
│ 🔄 代码复用 - 避免重复编写公共部分 │
│ │
│ 🎨 一致样式 - 保持网站视觉一致性 │
│ │
│ 📦 易于维护 - 修改一处,全局生效 │
│ │
└─────────────────────────────────────────────────────┘
二、基础布局 #
2.1 创建基础布局 #
astro
---
// src/layouts/Layout.astro
interface Props {
title: string;
description?: string;
}
const {
title,
description = '默认描述'
} = Astro.props;
---
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content={description} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>
<style is:global>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, sans-serif;
line-height: 1.6;
}
</style>
2.2 使用布局 #
astro
---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro';
---
<Layout title="首页" description="欢迎来到我的网站">
<h1>欢迎</h1>
<p>这是首页内容。</p>
</Layout>
三、完整布局示例 #
3.1 带导航和页脚的布局 #
astro
---
// src/layouts/Layout.astro
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
interface Props {
title: string;
description?: string;
image?: string;
}
const {
title,
description = '默认描述',
image = '/og-image.png'
} = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
---
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- SEO Meta -->
<meta name="description" content={description} />
<meta name="generator" content={Astro.generator} />
<link rel="canonical" href={canonicalURL} />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:url" content={canonicalURL} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={image} />
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
</head>
<body>
<Header />
<main>
<slot />
</main>
<Footer />
</body>
</html>
<style is:global>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #1f2937;
background: #fff;
min-height: 100vh;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
</style>
3.2 Header 组件 #
astro
---
// src/components/Header.astro
const navItems = [
{ label: '首页', href: '/' },
{ label: '博客', href: '/blog' },
{ label: '关于', href: '/about' },
];
const currentPath = Astro.url.pathname;
---
<header class="header">
<nav class="nav container">
<a href="/" class="logo">
<span class="logo-text">My Site</span>
</a>
<ul class="nav-list">
{navItems.map(item => (
<li>
<a
href={item.href}
class:list={['nav-link', { active: currentPath === item.href }]}
>
{item.label}
</a>
</li>
))}
</ul>
</nav>
</header>
<style>
.header {
background: #fff;
border-bottom: 1px solid #e5e7eb;
position: sticky;
top: 0;
z-index: 100;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.nav {
display: flex;
justify-content: space-between;
align-items: center;
height: 64px;
}
.logo {
font-size: 1.5rem;
font-weight: bold;
color: #2563eb;
text-decoration: none;
}
.nav-list {
display: flex;
list-style: none;
gap: 1.5rem;
}
.nav-link {
padding: 0.5rem 1rem;
border-radius: 6px;
text-decoration: none;
color: #4b5563;
transition: all 0.2s;
}
.nav-link:hover {
background: #f3f4f6;
color: #1f2937;
}
.nav-link.active {
background: #eff6ff;
color: #2563eb;
}
</style>
3.3 Footer 组件 #
astro
---
// src/components/Footer.astro
const year = new Date().getFullYear();
const links = [
{ label: 'GitHub', href: 'https://github.com' },
{ label: 'Twitter', href: 'https://twitter.com' },
{ label: 'Email', href: 'mailto:hello@example.com' },
];
---
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-info">
<p class="copyright">© {year} My Site. All rights reserved.</p>
<p class="built-with">Built with Astro</p>
</div>
<div class="footer-links">
{links.map(link => (
<a
href={link.href}
target="_blank"
rel="noopener noreferrer"
class="footer-link"
>
{link.label}
</a>
))}
</div>
</div>
</div>
</footer>
<style>
.footer {
background: #f9fafb;
border-top: 1px solid #e5e7eb;
padding: 2rem 0;
margin-top: auto;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.footer-info {
color: #6b7280;
}
.copyright {
font-size: 0.875rem;
}
.built-with {
font-size: 0.75rem;
margin-top: 0.25rem;
}
.footer-links {
display: flex;
gap: 1.5rem;
}
.footer-link {
color: #6b7280;
text-decoration: none;
font-size: 0.875rem;
transition: color 0.2s;
}
.footer-link:hover {
color: #2563eb;
}
</style>
四、布局继承 #
4.1 基础布局 #
astro
---
// src/layouts/BaseLayout.astro
interface Props {
title: string;
}
const { title } = Astro.props;
---
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>
4.2 继承布局 #
astro
---
// src/layouts/BlogLayout.astro
import BaseLayout from './BaseLayout.astro';
import Sidebar from '../components/Sidebar.astro';
interface Props {
title: string;
author: string;
date: string;
}
const { title, author, date } = Astro.props;
---
<BaseLayout title={title}>
<article class="blog-post">
<header class="post-header">
<h1>{title}</h1>
<p class="meta">
<span>作者: {author}</span>
<span>日期: {date}</span>
</p>
</header>
<div class="post-content">
<slot />
</div>
</article>
<Sidebar />
</BaseLayout>
<style>
.blog-post {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
.post-header {
margin-bottom: 2rem;
}
.meta {
color: #6b7280;
font-size: 0.875rem;
}
.meta span {
margin-right: 1rem;
}
</style>
4.3 使用继承布局 #
astro
---
// src/pages/blog/[slug].astro
import BlogLayout from '../../layouts/BlogLayout.astro';
const { slug } = Astro.params;
const post = await getPost(slug);
---
<BlogLayout
title={post.title}
author={post.author}
date={post.date}
>
<p>{post.content}</p>
</BlogLayout>
五、多布局系统 #
5.1 不同页面类型使用不同布局 #
text
src/layouts/
├── Layout.astro # 基础布局
├── BlogLayout.astro # 博客布局
├── DocsLayout.astro # 文档布局
└── AdminLayout.astro # 管理后台布局
5.2 文档布局示例 #
astro
---
// src/layouts/DocsLayout.astro
import Layout from './Layout.astro';
import Sidebar from '../components/DocsSidebar.astro';
import TOC from '../components/TOC.astro';
interface Props {
title: string;
description?: string;
}
const { title, description } = Astro.props;
const headings = Astro.props.headings || [];
---
<Layout title={title} description={description}>
<div class="docs-container">
<aside class="sidebar">
<Sidebar />
</aside>
<main class="docs-main">
<article class="docs-content">
<slot />
</article>
</main>
<aside class="toc">
<TOC headings={headings} />
</aside>
</div>
</Layout>
<style>
.docs-container {
display: grid;
grid-template-columns: 250px 1fr 200px;
max-width: 1400px;
margin: 0 auto;
min-height: calc(100vh - 64px);
}
.sidebar {
border-right: 1px solid #e5e7eb;
padding: 2rem 1rem;
}
.docs-main {
padding: 2rem;
}
.toc {
border-left: 1px solid #e5e7eb;
padding: 2rem 1rem;
}
@media (max-width: 1024px) {
.docs-container {
grid-template-columns: 250px 1fr;
}
.toc {
display: none;
}
}
@media (max-width: 768px) {
.docs-container {
grid-template-columns: 1fr;
}
.sidebar {
display: none;
}
}
</style>
六、布局插槽 #
6.1 具名插槽 #
astro
---
// src/layouts/Layout.astro
---
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<slot name="head" />
</head>
<body>
<slot name="header" />
<main>
<slot />
</main>
<slot name="footer" />
</body>
</html>
6.2 使用具名插槽 #
astro
---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro';
---
<Layout>
<meta slot="head" name="keywords" content="astro, tutorial" />
<header slot="header">
<h1>网站标题</h1>
</header>
<p>这是主要内容。</p>
<footer slot="footer">
<p>自定义页脚</p>
</footer>
</Layout>
七、布局最佳实践 #
7.1 布局结构建议 #
text
推荐布局结构:
src/
├── layouts/
│ ├── Layout.astro # 基础布局
│ ├── BlogLayout.astro # 博客布局
│ └── DocsLayout.astro # 文档布局
├── components/
│ ├── Header.astro # 导航头部
│ ├── Footer.astro # 页脚
│ ├── Sidebar.astro # 侧边栏
│ └── ...
└── pages/
├── index.astro
├── blog/
│ └── [slug].astro
└── docs/
└── [...path].astro
7.2 Props 设计 #
astro
---
// 好的 Props 设计
interface Props {
title: string;
description?: string;
image?: string;
noIndex?: boolean;
}
// 避免
interface Props {
data: any; // 太宽泛
}
7.3 SEO 优化 #
astro
---
// src/layouts/Layout.astro
interface Props {
title: string;
description?: string;
image?: string;
canonicalURL?: string;
noIndex?: boolean;
}
const {
title,
description,
image,
canonicalURL,
noIndex = false
} = Astro.props;
const siteTitle = 'My Site';
const fullTitle = title ? `${title} | ${siteTitle}` : siteTitle;
---
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{description && <meta name="description" content={description} />}
{noIndex && <meta name="robots" content="noindex, nofollow" />}
{canonicalURL && <link rel="canonical" href={canonicalURL} />}
<!-- Open Graph -->
<meta property="og:title" content={fullTitle} />
{description && <meta property="og:description" content={description} />}
{image && <meta property="og:image" content={image} />}
<title>{fullTitle}</title>
</head>
<body>
<slot />
</body>
</html>
八、总结 #
布局组件核心要点:
text
┌─────────────────────────────────────────────────────┐
│ 布局组件要点 │
├─────────────────────────────────────────────────────┤
│ │
│ 🎯 统一结构 定义页面公共结构 │
│ │
│ 🔄 代码复用 避免重复编写 │
│ │
│ 📦 插槽机制 灵活插入内容 │
│ │
│ 🏗️ 布局继承 多层布局嵌套 │
│ │
│ 🎨 样式隔离 组件级样式管理 │
│ │
└─────────────────────────────────────────────────────┘
下一步,让我们学习 Astro配置,掌握项目配置方法!
最后更新:2026-03-28