Astro ã§ããã°ãäœãå®å šãã¥ãŒããªã¢ã« â ãããžã§ã¯ãäœæãããããã€ãŸã§ã2026幎çã
Astroã§ããã°ãµã€ããäœãæ¹æ³ãã¹ããããã€ã¹ãããã§è§£èª¬ããããžã§ã¯ãäœæãMarkdownã§ã®èšäºç®¡çãSEOèšå®ãPagefindæ€çŽ¢å°å ¥ããããã€ãŸã§å®äœéšããŒã¹ã§ãŸãšããŸããã
Astro ããããã°ã«æé©ãªéçãµã€ããã¬ãŒã ã¯ãŒã¯ããšèšããçç±
ãNext.js ã Nuxt ã§ããã°ãäœã£ããã©ããªãŒããŒãšã³ãžãã¢ãªã³ã°ã ã£ãâŠã çè ããŸãã«ãã®ãã¿ãŒã³ã§ãNext.js ã§äœã£ãããã°ã® JS ãã³ãã«ãµã€ãºã«æ©ãã æ«ã« Astro ã«ä¹ãæããŸãããAstro ã¯ã³ã³ãã³ãäžå¿ã®ãµã€ãã«ç¹åãããã¬ãŒã ã¯ãŒã¯ã§ãããã°æ§ç¯ã«æé©ã§ãã
ãã®èšäºã§ã¯ãåœãµã€ãïŒtoolcraftlab.devïŒã Astro ã§æ§ç¯ããå®äœéšãããŒã¹ã«ããããžã§ã¯ãäœæãããããã€ãŸã§ãäžæ°é貫ã§è§£èª¬ããŸãã2026å¹Žææ°ã® Astro ã®æ©èœã掻çšããé«éã»SEOæé©åãããããã°ãäœããŸãããã
ãªã Astro ãããã°ã«æé©ãªã®ã
Astro ã®ç¹åŸŽ
| ç¹åŸŽ | 説æ |
|---|---|
| ãŒã JavaScriptïŒããã©ã«ãïŒ | ã¯ã©ã€ã¢ã³ãã«äžèŠãªJSãéããã衚瀺ãé«é |
| ã³ã³ãã³ãã³ã¬ã¯ã·ã§ã³ | Markdown / MDX ãåå®å šã«ç®¡ç |
| ã¢ã€ã©ã³ãã¢ãŒããã¯ã㣠| å¿ èŠãªéšåã ãã€ã³ã¿ã©ã¯ãã£ãã«ã§ãã |
| ãã«ããã¬ãŒã ã¯ãŒã¯å¯Ÿå¿ | React, Vue, Svelte, Solid ã®ã³ã³ããŒãã³ããæ··åšå¯èœ |
| SSG / SSR äž¡å¯Ÿå¿ | éççæãšãµãŒããŒãµã€ãã¬ã³ããªã³ã°ãéžã¹ã |
| çµã¿èŸŒã¿ã®æé©å | ç»åæé©åãCSSå§çž®ãHTMLæå°åãæšæºæèŒ |
ä»ã®ãã¬ãŒã ã¯ãŒã¯ãšã®æ¯èŒïŒããã°çšéïŒ
| é ç® | Astro | Next.js | Hugo | Gatsby |
|---|---|---|---|---|
| åŠç¿ã³ã¹ã | äœ | äž | äž | é« |
| ãã«ãé床 | â | â | â | â³ |
| JS ãã³ãã«ãµã€ãº | æ¥µå° | 倧 | ãªã | 倧 |
| Markdown ãµããŒã | â | â | â | â |
| ç»åæé©å | çµã¿èŸŒã¿ | çµã¿èŸŒã¿ | æå | ãã©ã°ã€ã³ |
| ãšã³ã·ã¹ãã | â | â | â | â |
| åå®å šãªã³ã³ãã³ã | â | æå | â | æå |
Step 1: ãããžã§ã¯ãäœæ
Astro ãããžã§ã¯ãã®åæå
# Astro ãããžã§ã¯ããäœæ
npm create astro@latest my-blog
# 察話åã»ããã¢ããã®åçäŸ
# â astro v5.x
# â
# â How would you like to start your new project?
# â Use blog template
# â
# â Install dependencies?
# â Yes
# â
# â Do you plan to write TypeScript?
# â Yes
# â
# â How strict should TypeScript be?
# â Strict
# â
# â Initialize a new git repository?
# â Yes
# â
cd my-blog
ãããžã§ã¯ãæ§é
my-blog/
âââ src/
â âââ components/ # åå©çšå¯èœãªã³ã³ããŒãã³ã
â â âââ Header.astro
â â âââ Footer.astro
â â âââ BlogCard.astro
â â âââ SEO.astro
â âââ content/ # ã³ã³ãã³ãã³ã¬ã¯ã·ã§ã³
â â âââ blog/ # ããã°èšäºïŒMarkdown / MDXïŒ
â â â âââ first-post.md
â â â âââ second-post.mdx
â â âââ config.ts # ã³ã¬ã¯ã·ã§ã³å®çŸ©
â âââ layouts/ # ã¬ã€ã¢ãŠããã³ãã¬ãŒã
â â âââ BaseLayout.astro
â â âââ BlogPost.astro
â âââ pages/ # ããŒãžïŒãã¡ã€ã«ããŒã¹ã«ãŒãã£ã³ã°ïŒ
â â âââ index.astro
â â âââ blog/
â â â âââ index.astro
â â â âââ [...slug].astro
â â âââ about.astro
â âââ styles/ # ã°ããŒãã«ã¹ã¿ã€ã«
â â âââ global.css
â âââ assets/ # ç»åãªã©ã®éçã¢ã»ãã
â âââ hero.jpg
âââ public/ # ãã®ãŸãŸé
ä¿¡ãããéçãã¡ã€ã«
â âââ favicon.svg
â âââ robots.txt
âââ astro.config.mjs # Astro èšå®ãã¡ã€ã«
âââ tsconfig.json
âââ package.json
Step 2: ã³ã³ãã³ãã³ã¬ã¯ã·ã§ã³ã®èšå®
ã³ã¬ã¯ã·ã§ã³å®çŸ©
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: ({ image }) =>
z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: image().optional(),
category: z.string(),
tags: z.array(z.string()).default([]),
author: z.string().default('Author'),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
èšäºã®æžãæ¹ïŒMarkdown / MDXïŒ
---
title: "ã¯ãããŠã®ããã°èšäº"
description: "Astro ã§äœã£ãããã°ã®æåã®èšäºã§ãã"
pubDate: "2026-04-01"
heroImage: "../../assets/hero.jpg"
category: "tech"
tags: ["Astro", "ããã°"]
author: "ToolCraft Lab"
---
## èŠåºã
æ¬æãããã«æžããŸãã**倪å**ã*æäœ*ã䜿ããŸãã
### ã³ãŒããããã¯ã察å¿
\```javascript
console.log("Hello, Astro!");
\```
Step 3: ããã°èšäºäžèЧããŒãžã®äœæ
èšäºäžèЧããŒãž
---
// src/pages/blog/index.astro
import BaseLayout from '../../layouts/BaseLayout.astro';
import BlogCard from '../../components/BlogCard.astro';
import { getCollection } from 'astro:content';
const posts = (await getCollection('blog'))
.filter((post) => !post.data.draft)
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---
<BaseLayout title="ããã°èšäºäžèЧ" description="ãã¹ãŠã®ããã°èšäº">
<h1>ããã°</h1>
<div class="post-grid">
{posts.map((post) => (
<BlogCard post={post} />
))}
</div>
</BaseLayout>
<style>
.post-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
</style>
ããã°ã«ãŒãã³ã³ããŒãã³ã
---
// src/components/BlogCard.astro
import { Image } from 'astro:assets';
import type { CollectionEntry } from 'astro:content';
interface Props {
post: CollectionEntry<'blog'>;
}
const { post } = Astro.props;
const { title, description, pubDate, heroImage, tags } = post.data;
---
<article class="card">
<a href={`/blog/${post.id}/`}>
{heroImage && (
<Image
src={heroImage}
alt={title}
width={720}
height={360}
class="card-image"
/>
)}
<div class="card-content">
<time datetime={pubDate.toISOString()}>
{pubDate.toLocaleDateString('ja-JP')}
</time>
<h2>{title}</h2>
<p>{description}</p>
<div class="tags">
{tags.map((tag) => (
<span class="tag">{tag}</span>
))}
</div>
</div>
</a>
</article>
èšäºè©³çްããŒãž
---
// src/pages/blog/[...slug].astro
import { getCollection, render } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
const post = Astro.props;
const { Content } = await render(post);
---
<BlogPost {...post.data}>
<Content />
</BlogPost>
Step 4: SEO èšå®
SEO ã¡ã¿ã¿ã°ã®æžãæ¹ã詳ããç¥ãããæ¹ã¯ãSEOã¡ã¿ã¿ã°å®å šã¬ã€ããããOGPç»åã®ãµã€ãºèšå®ã¯ãOGPç»åãµã€ãºã¬ã€ãããåç §ããŠãã ããã
SEO ã³ã³ããŒãã³ã
---
// src/components/SEO.astro
interface Props {
title: string;
description: string;
image?: string;
type?: string;
publishedTime?: Date;
}
const {
title,
description,
image = '/og-default.jpg',
type = 'website',
publishedTime,
} = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const ogImageURL = new URL(image, Astro.site);
---
<!-- åºæ¬ã¡ã¿ã¿ã° -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonicalURL} />
<!-- Open Graph -->
<meta property="og:type" content={type} />
<meta property="og:url" content={canonicalURL} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={ogImageURL} />
<meta property="og:site_name" content="ToolCraft Lab" />
<meta property="og:locale" content="ja_JP" />
{publishedTime && (
<meta
property="article:published_time"
content={publishedTime.toISOString()}
/>
)}
<!-- 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={ogImageURL} />
ãµã€ããããã®èšå®
# ãµã€ããããã€ã³ãã°ã¬ãŒã·ã§ã³ã远å
npx astro add sitemap
// astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://your-domain.com',
integrations: [
sitemap({
filter: (page) => !page.includes('/draft/'),
}),
],
});
robots.txt
# public/robots.txt
User-agent: *
Allow: /
Sitemap: https://your-domain.com/sitemap-index.xml
RSS ãã£ãŒãã®èšå®
# RSS ã€ã³ãã°ã¬ãŒã·ã§ã³ã远å
npm install @astrojs/rss
// src/pages/rss.xml.ts
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context: { site: URL }) {
const posts = (await getCollection('blog'))
.filter((post) => !post.data.draft)
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
return rss({
title: 'ToolCraft Lab Blog',
description: 'ãšã³ãžãã¢ã®ããã®å®è·µçãªæè¡ããã°',
site: context.site,
items: posts.map((post) => ({
title: post.data.title,
pubDate: post.data.pubDate,
description: post.data.description,
link: `/blog/${post.id}/`,
})),
});
}
Step 5: Pagefind ã§å šææ€çŽ¢ãå°å ¥
Pagefind ãšã¯
Pagefind ã¯éçãµã€ãåãã®å šææ€çŽ¢ã©ã€ãã©ãªã§ãããã«ãæã«ã€ã³ããã¯ã¹ãçæããã¯ã©ã€ã¢ã³ããµã€ãã§é«éæ€çŽ¢ãå®çŸããŸãããµãŒããŒãäžèŠãªã®ã§ãCloudflare Pages ãªã©ã®éçãã¹ãã£ã³ã°ã§ããã®ãŸãŸåããŸãã
ã€ã³ã¹ããŒã«ãšèšå®
# Pagefind ãã€ã³ã¹ããŒã«
npm install -D pagefind
// package.json ã®ã¹ã¯ãªãããæŽæ°
{
"scripts": {
"build": "astro build && npx pagefind --site dist",
"dev": "astro dev",
"preview": "astro preview"
}
}
æ€çŽ¢UIã®è¿œå
---
// src/components/Search.astro
---
<div id="search" class="search-container"></div>
<link href="/pagefind/pagefind-ui.css" rel="stylesheet" />
<script>
import { PagefindUI } from '/pagefind/pagefind-ui.js';
window.addEventListener('DOMContentLoaded', () => {
new PagefindUI({
element: '#search',
showSubResults: true,
translations: {
placeholder: 'èšäºãæ€çŽ¢...',
zero_results: 'ã[SEARCH_TERM]ãã«äžèŽããèšäºãèŠã€ãããŸããã§ãã',
},
});
});
</script>
<style>
.search-container {
max-width: 640px;
margin: 2rem auto;
}
</style>
æ€çŽ¢å¯Ÿè±¡ã®å¶åŸ¡
<!-- æ€çŽ¢ã«å«ãããèŠçŽ -->
<article data-pagefind-body>
<h1>èšäºã¿ã€ãã«</h1>
<p>ãã®å
å®¹ã¯æ€çŽ¢å¯Ÿè±¡ã«ãªããŸãã</p>
</article>
<!-- æ€çŽ¢ããé€å€ãããèŠçŽ -->
<nav data-pagefind-ignore>
ããã²ãŒã·ã§ã³ã¯æ€çŽ¢å¯Ÿè±¡å€
</nav>
Step 6: ããã©ãŒãã³ã¹æé©å
ç»åæé©å
Astro ã® <Image> ã³ã³ããŒãã³ãã䜿ããšãç»åãèªåçã«æé©åãããŸãã
---
import { Image } from 'astro:assets';
import heroImage from '../assets/hero.jpg';
---
<!-- èªåçã« WebP/AVIF ã«å€æãã¬ã¹ãã³ã·ãå¯Ÿå¿ -->
<Image
src={heroImage}
alt="ããŒããŒç»å"
width={1200}
height={630}
loading="lazy"
decoding="async"
/>
ãã©ã³ãæé©å
ã¹ã¿ã€ãªã³ã°ã« Tailwind CSS ã䜿ãããå Žåã¯ãTailwind CSS v4 ã®æ°æ©èœãŸãšããããã§ãã¯ããŠãã ããã
/* src/styles/global.css */
/* Google Fonts ã®æé©ãªèªã¿èŸŒã¿ */
@font-face {
font-family: 'Noto Sans JP';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/NotoSansJP-Regular.woff2') format('woff2');
unicode-range: U+3000-9FFF, U+F900-FAFF;
}
View TransitionsïŒããŒãžé·ç§»ã¢ãã¡ãŒã·ã§ã³ïŒ
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
---
<html lang="ja">
<head>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>
Step 7: ãããã€
Cloudflare Pages ãžã®ãããã€æé ãããã«è©³ããç¥ãããæ¹ã¯ãCloudflare Pages å®å šã¬ã€ãããåèã«ããŠãã ããã
Cloudflare Pages ãžã®ãããã€
# Wrangler CLI ã§ãããã€
npm install -g wrangler
wrangler login
wrangler pages deploy dist --project-name my-blog
GitHub 飿ºã§ã®èªåãããã€
1. Cloudflare ããã·ã¥ããŒã â Workers & Pages â Create
2. Pages â Connect to Git
3. GitHub ãªããžããªãéžæ
4. ãã«ãèšå®:
- Framework: Astro
- Build command: npm run build
- Output directory: dist
5. Save and Deploy
以é㯠main ãã©ã³ããžã® push ã§èªåãããã€ãããŸãã
å®è·µ TipsïŒåœãµã€ãã®æ§ç¯çµéšããïŒ
1. MDX ãæŽ»çšããŠã€ã³ã¿ã©ã¯ãã£ããªèšäºãæžã
# MDX ã€ã³ãã°ã¬ãŒã·ã§ã³ã远å
npx astro add mdx
MDX ã䜿ããšãMarkdown ã®äžã« Astro / React / Vue ã³ã³ããŒãã³ããåã蟌ããŸãã
2. ã«ããŽãªã»ã¿ã°ããŒãžã®èªåçæ
---
// src/pages/tags/[tag].astro
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
const tags = [...new Set(posts.flatMap((post) => post.data.tags))];
return tags.map((tag) => ({
params: { tag },
props: {
posts: posts.filter((post) => post.data.tags.includes(tag)),
},
}));
}
const { tag } = Astro.params;
const { posts } = Astro.props;
---
<h1>ã¿ã°: {tag}</h1>
<!-- èšäºäžèЧã衚瀺 -->
3. äžæžãæ©èœ
// frontmatter ã« draft: true ã远å
// ã³ã¬ã¯ã·ã§ã³ååŸæã«ãã£ã«ã¿
const publishedPosts = (await getCollection('blog'))
.filter((post) => !post.data.draft);
4. ååŸã®èšäºãžã®ããã²ãŒã·ã§ã³
---
const posts = (await getCollection('blog'))
.sort((a, b) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf());
const currentIndex = posts.findIndex((p) => p.id === post.id);
const prevPost = posts[currentIndex - 1];
const nextPost = posts[currentIndex + 1];
---
<nav class="post-navigation">
{prevPost && <a href={`/blog/${prevPost.id}/`}>â {prevPost.data.title}</a>}
{nextPost && <a href={`/blog/${nextPost.id}/`}>{nextPost.data.title} â</a>}
</nav>
ãŸãšã â Astro ã§ããã°ãäœã䟡å€
Astro ã§ããã°ãäœãæå€§ã®ã¡ãªããã¯ãã³ã³ãã³ãã«éäžã§ãããããšã§ãã
- é«é衚瀺: ãŒã JavaScript ã§Lighthouseã¹ã³ã¢ãã»ãŒæºç¹
- åå®å šãªã³ã³ãã³ã管ç: ã¹ããŒãå®çŸ©ã§ frontmatter ã®ãã¹ã鲿¢
- æè»ãªæ¡åŒµ: å¿ èŠã«å¿ã㊠React ã Vue ã®ã³ã³ããŒãã³ãã远å å¯èœ
- åªããDX: Hot Module Replacement ã§å¿«é©ãªå·çäœéš
- Pagefind æ€çŽ¢: ãµãŒããŒãªãã§å šææ€çŽ¢ãå®çŸ
åœãµã€ãã Astro + Cloudflare Pages ã®æ§æã§éçšããŠãããæéã³ã¹ããŒãã§é«éãªããã°ãµã€ããå®çŸããŠããŸãããã²è©ŠããŠã¿ãŠãã ããã