Astro v5.3入門

2024-2025 Shunsuke Watanabe

このチュートリアルについて

このチュートリアルは Astroを静的サイトジェネレータとして使う方法に触れてもらうことを目的としています。

SSRやServer Islandsは扱いません。

Astroは様々なサイトで利用されているコンテンツ志向のフレームワークです



👨‍💻サンプルコード → https://github.com/craftgear/astro_tutorial

discord → https://discord.gg/3havwjWGsw

bluesky → https://bsky.app/profile/craftgear.bsky.social

📝 blog → https://craftgear.github.io/posts/

プロジェクト初期化

ディレクトリ構成

— astro_tutorial
├── public/
│  └── favicon.svg
├── src/
│  └── pages/
│     └── index.astro
├── astro.config.mjs
├── package.json
├── README.md
└── tsconfig.json

パスエイリアスの設定

tailwindcssのインストール

https://docs.astro.build/en/guides/styling/#tailwind

tailwindcssのプラグインを追加

  1. typographyとdaisyUIをインストール npm i -D @tailwindcss/typography daisyui@beta

  2. global.css でtypographyとdaisyUIをインポート

    @import 'tailwindcss';
    @plugin 'daisyui';
    @plugin '@tailwindcss/typography';
  3. Layout.astrobodyタグにクラスを追加

    ...
    <body class="prose">
    ...

開発用サーバ起動

HTMLファイル生成

ページ作成

ファイルベースルーティング

  1. /src/pages/index.astro ファイルをひらいて <Welcome /> コンポーネントを消し、<h1>Index</h1>を追加する。
  2. /src/pages/posts/index.astro ファイルを新規作成し、<h1>Posts</h1> を追加する。

リンク

  1. /src/pages/index.astro から /src/pages/posts/index.astro にリンクを作成する

レイアウト & Slot

  1. /src/layouts/Base.astro を作成する

    ---
    import '@styles/global.css'
    ---
    <html lang="ja">
    <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content={Astro.generator} />
    <title>Astro</title>
    </head>
    <body
    class="prose flex min-h-screen max-w-full flex-col bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100">
    <nav
    class="flex justify-center gap-4 bg-white/90 py-4 backdrop-blur-sm dark:bg-slate-950/90"
    >
    <a
    href="/"
    class="active:scale-98 rounded-lg px-4 py-2 font-medium text-slate-900 no-underline transition-all hover:bg-indigo-100 hover:text-indigo-700 dark:text-slate-100 dark:hover:bg-indigo-800/20 dark:hover:text-indigo-300"
    >
    Top
    </a>
    <a
    href="/posts"
    class="active:scale-98 rounded-lg px-4 py-2 font-medium text-slate-900 no-underline transition-all hover:bg-indigo-200 hover:text-indigo-800 dark:text-slate-100 dark:hover:bg-indigo-800/30 dark:hover:text-indigo-400"
    >
    Posts
    </a>
    </nav>
    <main class="mx-auto my-8 w-full max-w-[1200px] flex-1 px-4 flex flex-col items-center">
    <slot />
    </main>
    </body>
    </html>
  2. /src/pages/index.astro/src/pages/posts/index.astro でこのレイアウトを利用する

ダイナミックルート

  1. /src/pages/posts/[slug].astro を作成し、post-1 と post-2 という2つのファイルを生成する

    ---
    import Base from '@layouts/Base.astro';
    export const getStaticPaths = () => {
    return [
    { params: { slug: 'post-1'} },
    { params: { slug: 'post-2'} }
    ];
    }
    const {slug} = Astro.params;
    ---
    <Base>
    <h1>{slug}</h1>
    </Base>
  2. http://localhost:4321/posts/post-1 にアクセスし、ページが生成されていることを確認する

Transition

  1. /src/layouts/Base.astro にトランジションの設定を追加

    ---
    import { ClientRouter } from 'astro:transitions'
    ---
    <html lang="jp">
    <head>
    ...
    <ClientRouter fallback="none" />
    </head>
    <body>
    <main transition:animate="slide">
    ...
    </main>
    </body>
    </html>

GoogleAnalytics

Astroコンポーネント

Reactとの違い

コンポーネントスクリプト

コンポーネントテンプレート

Props

ナビゲーションをコンポーネントに切り出す

  1. src/components/Header.astro ファイルを作成し、src/layouts/Base.astro からヘッダ部分を切り出す

    ---
    ---
    <nav class="flex justify-center gap-4 bg-white/90 py-4 backdrop-blur-sm dark:bg-slate-950/90">
    <a
    href="/"
    class="active:scale-98 rounded-lg px-4 py-2 font-medium text-slate-900 no-underline transition-all hover:bg-indigo-
    100 hover:text-indigo-700 dark:text-slate-100 dark:hover:bg-indigo-800/20 dark:hover:text-indigo-300"
    >
    Top
    </a>
    <a
    href="/posts"
    class="active:scale-98 rounded-lg px-4 py-2 font-medium text-slate-900 no-underline transition-all hover:bg-indigo-
    200 hover:text-indigo-800 dark:text-slate-100 dark:hover:bg-indigo-800/30 dark:hover:text-indigo-400"
    >
    Posts
    </a>
    </nav>
  2. HeaderコンポーネントをBaseレイアウトでインポートして表示する

    ---
    import { ClientRouter } from 'astro:transitions'
    import Header from '@components/Header.astro'
    ---
    <html lang="jp" class="h-full">
    ...
    <body>
    <Header />
    <main class="mx-auto my-8 max-w-[1200px] px-4" transition:animate="slide">
    <slot />
    </main>
    </body>
    </html>

スタイル

CSSファイルとstyle タグでスタイルを変更する

  1. src/pages/Index.astro を開き、<span>style this</span> を追加する

  2. src/styles/global.css ファイルにスタイルを追加する

    @import 'tailwindcss';
    @plugin 'daisyui';
    @plugin '@tailwindcss/typography';
    span {
    color: var(--color-orange-300);
    }
    a {
    text-decoration-line: none !important;
    }
    a:hover {
    color: var(--color-accent);
    }
  3. style thisの文字色と、navのホバー時の文字色が変わっていることを確認する

  4. src/pages/Index.astro に以下のスタイルを追加する

    ---
    import Base from '../layouts/Base.astro'
    ---
    <style>
    span {
    color: var(--color-green-300);
    }
    </style>
    <Base>
    <h1>Index Page</h1>
    <span>style this</span>
    <a href="posts/">Posts</a>
    </Base>
  5. 文字色が緑に変わるのを確認する

スタイルの優先度は、高い順に <style>タグ > class 属性 > グローバルcss > モジュールcss

<style>タグ ≫ モジュールcss ≫ グローバルcss ≫ class 属性

SVGコンポーネント

https://docs.astro.build/en/reference/experimental-flags/svg/

  1. astro.config.mjs に svgオプションを設定

    import { defineConfig } from 'astro/config'
    import tailwind from '@astrojs/tailwind'
    // https://astro.build/config
    export default defineConfig({
    integrations: [tailwind()],
    experimental: {
    svg: {
    mode: 'inline',
    },
    },
    })
  2. src/assets/astro.svg を開き、以下の内容で置き換える

    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
    <g>
    <path d="M47.7 107.1c-5.5-5-7.2-15.7-4.9-23.4 4 4.9 9.6 6.4 15.4 7.3 8.9 1.3 17.6.8 25.9-3.2l2.8-1.7a18 18 0 0 1-7.2 20l-5.5 3.8c-5.6 3.8-7.2 8.2-5 14.7l.2.7a14 14 0 0 1-6.6-5.6 15.8 15.8 0 0 1-2.6-8.6c0-1.5 0-3-.2-4.5-.5-3.7-2.2-5.3-5.5-5.4-3.3-.1-5.9 2-6.6 5.2l-.2.7Z"/>
    <path d="M16 82.4s16.5-8 33-8l12.4-38.3c.5-2 1.8-3.2 3.3-3.2 1.6 0 3 1.3 3.4 3.2l12.4 38.3c19.6 0 33 8 33 8l-28-76c-.8-2.3-2.2-3.7-4-3.7H48c-1.8 0-3.1 1.4-4 3.7l-28 76Z"/>
    </g>
    <path fill="url(#a)" d="M47.7 107.1c-5.5-5-7.2-15.7-4.9-23.4 4 4.9 9.6 6.4 15.4 7.3 8.9 1.3 17.6.8 25.9-3.2l2.8-1.7a18 18 0 0 1-7.2 20l-5.5 3.8c-5.6 3.8-7.2 8.2-5 14.7l.2.7a14 14 0 0 1-6.6-5.6 15.8 15.8 0 0 1-2.6-8.6c0-1.5 0-3-.2-4.5-.5-3.7-2.2-5.3-5.5-5.4-3.3-.1-5.9 2-6.6 5.2l-.2.7Z"/>
    <defs>
    <linearGradient id="a" x1="64.7" x2="77.4" y1="119.2" y2="77.4" gradientUnits="userSpaceOnUse">
    <stop stop-color="#D83333"/>
    <stop offset="1" stop-color="#F041FF"/>
    </linearGradient>
    </defs>
    <style>
    g {
    fill: #000;
    }
    @media (prefers-color-scheme: dark) {
    g {
    fill: #FFF;
    }
    }
    </style>
    </svg>
  3. src/components/Footer.astro を作成する

    ---
    import AstroIcon from '@assets/astro.svg'
    ---
    <footer class="p-8 w-full flex justify-center items-center gap-1">
    powered by <AstroIcon size={36} />
    </footer>
  4. Footer.astro を Baseレイアウトで表示する

補足1: <style> <script> タグにコンポーネントスクリプトで定義した変数を渡す方法

https://docs.astro.build/en/reference/directives-reference/#definevars

https://docs.astro.build/en/guides/client-side-scripts/

Content Layer Application

4.0 まで markdown → Content Collection → HTML

5.0 以降 データソース → Content Layer → Content Collection → HTML

Content collections

markdownの読み込み

  1. データソースのmarkdownファイルをプロジェクトルートに解凍する

    blog_examples.zip

  2. src/content.config.ts ファイルを新規作成

  3. content collectionの定義

    import { defineCollection, z } from "astro:content";
    const posts = defineCollection({
    });
    export const collections = { posts };
  4. loaderを追加

    import { defineCollection, z } from "astro:content";
    import { glob } from 'astro/loaders'
    const posts = defineCollection({
    loader: glob({ pattern: '**/*.md', base: 'src/../blog_examples' }),
    });
    export const collections = { posts };
  5. schemaを追加

    import { defineCollection, z } from 'astro:content'
    import { glob } from 'astro/loaders'
    const posts = defineCollection({
    loader: glob({ pattern: '**/*.(md|mdx)', base: 'src/../blog_examples' }),
    schema: z.object({
    title: z.string(),
    pubDate: z.coerce.date(),
    description: z.string(),
    heroImage: z.string(),
    }),
    })
    export const collections = { posts }
  6. npx astro sync を実行

Content Collectionからページを生成

  1. src/pages/posts/[slug].astro で posts collectionを読み込み、各記事ごとのHTMLを生成

    ---
    import Base from '@layouts/Base.astro'
    import { type CollectionEntry, getCollection, render } from 'astro:content'
    export const getStaticPaths = async () => {
    const posts = await getCollection('posts')
    return posts.map((post) => {
    return {
    params: { slug: post.id },
    props: post,
    }
    })
    }
    type Props = CollectionEntry<'posts'>
    const post = Astro.props
    const { Content } = await render(post)
    ---
    <Base>
    <Content />
    </Base>
  2. サーバーを再起動

  3. src/pages/posts/index.astro で記事一覧を表示する

    ---
    import Base from '@layouts/Base.astro'
    import { Image } from 'astro:assets'
    import { getCollection } from 'astro:content'
    import aboutImage from '../../../blog_examples/imgs/blog-placeholder-about.jpg'
    const posts = (await getCollection('posts')).sort(
    (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
    )
    ---
    <Base>
    <section>
    <ul class="flex flex-wrap justify-center gap-4 m-0 p-0">
    {
    posts.map((post) => (
    <li class="first:w-full first:flex first:flex-col first:items-center w-[calc(50%-2rem)]">
    <a href={`/posts/${post.id}/`}>
    <Image
    width={720}
    height={360}
    src={aboutImage}
    alt="post title image"
    />
    <h4 class="title">{post.data.title}</h4>
    <p class="date">{post.data.pubDate.toLocaleString()}</p>
    </a>
    </li>
    ))
    }
    </ul>
    </section>
    </Base>

補足:レンダリングされたマークダウンのスタイル適用

  1. src/pages/posts/_post.css ファイルを作成する

    h1 {
    font-size: xxx-large !important;
    color: coral;
    }
  2. src/pages/posts/[slug].astro_post.css をインポートする

コンポーネント方式 https://docs.astro.build/ja/recipes/tailwind-rendered-markdown/#recipe

Community loaders

https://astro.build/integrations/?search=&categories[]=loaders

Feed loader

  1. npm i @ascorbic/feed-loader

  2. src/content.config.ts にコレクションを追加

    import { defineCollection, z } from 'astro:content'
    import { glob } from 'astro/loaders'
    import { feedLoader } from '@ascorbic/feed-loader'
    const posts = defineCollection({
    loader: glob({ pattern: '**/*.md', base: 'src/../blog_examples' }),
    schema: z.object({
    title: z.string(),
    pubDate: z.coerce.date(),
    description: z.string(),
    heroImage: z.string(),
    }),
    })
    const news = defineCollection({
    loader: feedLoader({
    url: 'https://news.yahoo.co.jp/rss/categories/domestic.xml',
    }),
    schema: z.object({
    title: z.string(),
    pubdate: z.coerce.date(),
    link: z.string(),
    }),
    })
    export const collections = { posts, news }
  3. サーバーを再起動

  4. src/pages/news.astro ファイルを作成

    ---
    import { getCollection } from 'astro:content'
    import Base from '@layouts/Base.astro'
    const news = await getCollection('news')
    ---
    <Base>
    <ul class="flex flex-col items-center space-y-2 m-auto list-none">
    {
    news.map((x) => (
    <li class="w-full hover:bg-base-200">
    <a href={x.data.link} class="px-4 py-4 flex items-center">
    <div class="text-2xl font-bold text-right">
    {x.data.pubdate?.toLocaleString().slice(-8, -3)}
    </div>
    <div class="text-2xl text-left ml-4 pl-4 border-l-2
    border-base-content hover:border-accent ">
    {x.data.title}
    </div>
    </a>
    </li>
    ))
    }
    </ul>
    </Base>
  5. http://localhost:4321/news を開く

独自 Loaderの作成

独自ローダーとしてWikipediaで昨日最も読まれた記事を取得するLoaderを書いてみます。

https://api.wikimedia.org/wiki/Feed_API/Reference/Featured_content

Inline Loader の作成

  1. src/content.config.ts に新しいcontent collectionを追加

    const wikipedia = defineCollection({
    loader: async () => {},
    schema: z.object({}),
    })
  2. loader関数を作成

    type Mostread = {
    pageid: number
    }
    const wikipedia = defineCollection({
    loader: async () => {
    const [y, mm, dd] = yesterdayYMD()
    const res = await fetch(
    `https://api.wikimedia.org/feed/v1/wikipedia/ja/featured/${y}/${mm}/${dd}`,
    )
    const json = await res.json()
    return json.mostread.articles.map((x: Mostread) => ({
    id: `${x.pageid}`,
    ...x,
    }))
    },
    schema: z.object({}),
    })
    • loader関数で返す配列の要素には必ず id プロパティが必要

    • 前日の日付を返す関数を src/utils/date.ts に作成

      export const yesterdayYMD = () => {
      const date = new Date()
      const yesterday = date.setDate(date.getDate() - 1)
      const [y, m, d] = new Date(yesterday)
      .toLocaleString()
      .split(' ')[0]
      .split('/')
      const mm = `0${m}`.slice(-2)
      const dd = `0${d}`.slice(-2)
      return [y, mm, dd]
      }
  3. schemaを作成

    type Mostread = {
    pageid: number
    }
    const wikipedia = defineCollection({
    loader: async () => {
    const [y, mm, dd] = yesterdayString()
    const res = await fetch(
    `https://api.wikimedia.org/feed/v1/wikipedia/ja/featured/${y}/${mm}/${dd}`,
    )
    const json = await res.json()
    return json.mostread.articles.map((x: Mostread) => ({
    id: `${x.pageid}`,
    ...x,
    }))
    },
    schema: z.object({
    views: z.number(),
    rank: z.number(),
    type: z.string(),
    titles: z.object({
    canonical: z.string(),
    display: z.string(),
    }),
    thumbnail: z
    .object({
    source: z.string(),
    width: z.number(),
    height: z.number(),
    })
    .optional(),
    description: z.string().optional(),
    content_urls: z.object({
    desktop: z.object({
    page: z.string(),
    }),
    }),
    extract: z.string(),
    }),
    })
  4. 作成した content collection をエクスポート

    ...
    export const collections = { posts, news, wikipedia }
  5. src/pages/wikipedia.astro を作成

    ---
    import { type CollectionEntry, getCollection } from 'astro:content'
    import { Picture } from 'astro:assets'
    import Base from '@layouts/Base.astro'
    import { yesterdayYMD } from '@utils/date'
    type Item = CollectionEntry<'wikipedia'>
    const wikipedia = await getCollection('wikipedia')
    ---
    <Base>
    <div class="flex flex-col items-center">
    <h1 class="mb-8 text-xl">
    Wikipediaで{yesterdayYMD().join('/')}に最も読まれた日本語記事
    </h1>
    <div class="grid w-3/4 gap-8">
    {
    wikipedia.map((x: Item, i: number) => {
    return (
    <div class="card-side flex rounded-lg bg-base-300 shadow-xl">
    <a
    href={x.data.content_urls.desktop.page}
    class="card-body justify-center text-base-content"
    >
    <div class="flex gap-8">
    <span class="break-keep text-center font-serif text-4xl">
    {i + 1}
    </span>
    <div class="flex flex-col gap-4">
    <div class="flex flex-row items-center justify-start gap-1">
    <div class="card-title mr-4 text-3xl">
    {x.data.titles.canonical}
    </div>
    <svg
    xmlns="http://www.w3.org/2000/svg"
    width="20"
    height="20"
    viewBox="0 0 24 24"
    >
    <path
    fill="currentColor"
    d="M12 6.5A9.77 9.77 0 0 0 3.18 12c1.65 3.37 5.02 5.5 8.82 5.5s7.17-2.13 8.82-5.5A9.77 9.77 0 0 0 12 6.5m0 10c-2.48 0-4.5-2.02-4.5-4.5S9.52 7.5 12 7.5s4.5 2.02 4.5 4.5s-2.02 4.5-4.5 4.5"
    opacity=".3"
    />
    <path
    fill="currentColor"
    d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5m0 13A9.77 9.77 0 0 1 3.18 12C4.83 8.63 8.21 6.5 12 6.5s7.17 2.13 8.82 5.5A9.77 9.77 0 0 1 12 17.5m0-10c-2.48 0-4.5 2.02-4.5 4.5s2.02 4.5 4.5 4.5s4.5-2.02 4.5-4.5s-2.02-4.5-4.5-4.5m0 7a2.5 2.5 0 0 1 0-5a2.5 2.5 0 0 1 0 5"
    />
    </svg>
    <div>{x.data.views.toLocaleString()}</div>
    </div>
    <p>{x.data.extract}</p>
    </div>
    </div>
    </a>
    <div class="flex h-full flex-col justify-center bg-transparent">
    {x.data.thumbnail && (
    <Picture
    inferSize
    class="max-w-64 object-cover"
    src={x.data.thumbnail?.source}
    alt={x.data.titles.canonical}
    />
    )}
    </div>
    </div>
    )
    })
    }
    </div>
    </div>
    </Base>
  6. サーバーを再起動

  7. http://localhost:4321/wikipedia を開く

2. Object Loader の作成

  1. src/loaders/wikipedia.ts を作成する

    import type { Loader } from 'astro/loaders'
    export const wikipediaLoader = (): Loader => ({
    name: 'wikipedia',
    load: async () => {},
    schema: {},
    })
    • loader 関数ではなくて load 関数
    • Object Loaderには name で名前をつけられる
  2. load関数の中身を config.content.ts から移す

    import type { Loader } from 'astro/loaders'
    import { z } from 'astro:content'
    import { yesterdayYMD } from '@utils/date'
    type Mostread = {
    pageid: number;
    timestamp: string;
    }
    export const wikipediaLoader = (): Loader => ({
    name: 'wikipedia',
    load: async ({ store, generateDigest }) => {
    const [y, mm, dd] = yesterdayYMD()
    const res = await fetch(
    `https://api.wikimedia.org/feed/v1/wikipedia/ja/featured/${y}/${mm}/${dd}`,
    )
    const json = await res.json()
    store.clear()
    json.mostread.articles.forEach((x: Mostread) => {
    store.set({
    id: `${x.pageid}`,
    data: x,
    digest: generateDigest(x.timestamp),
    })
    })
    },
    schema: {},
    })
    • load関数では配列を返すのではなく、context.store にデータを保存する
  3. schemaを config.content.ts から移す

    import type { Loader } from 'astro/loaders'
    import { z } from 'astro:content'
    import { yesterdayString } from '@utils/day'
    type Mostread = {
    pageid: number
    }
    export const wikipediaLoader = (): Loader => ({
    name: 'wikipedia',
    load: async (context) => {
    const [y, mm, dd] = yesterdayString()
    const res = await fetch(
    `https://api.wikimedia.org/feed/v1/wikipedia/ja/featured/${y}/${mm}/${dd}`,
    )
    const json = await res.json()
    json.mostread.articles.forEach((x: Mostread) => {
    context.store.set({ id: `${x.pageid}`, data: x })
    })
    },
    schema: z.object({
    views: z.number(),
    rank: z.number(),
    type: z.string(),
    titles: z.object({
    canonical: z.string(),
    display: z.string(),
    }),
    thumbnail: z
    .object({
    source: z.string(),
    width: z.number(),
    height: z.number(),
    })
    .optional(),
    description: z.string().optional(),
    content_urls: z.object({
    desktop: z.object({
    page: z.string(),
    }),
    }),
    extract: z.string(),
    }),
    })
  4. src/config.content.ts で作成したローダーを読み込む

    import { defineCollection, z } from 'astro:content'
    import { glob } from 'astro/loaders'
    import { feedLoader } from '@ascorbic/feed-loader'
    import { authorFeedLoader } from '@ascorbic/bluesky-loader'
    import { wikipediaLoader } from '@loaders/wikipedia'
    ...
    const wikipedia = defineCollection({
    loader: wikipediaLoader(),
    })
    export const collections = { posts, news, timeline, wikipedia }
  5. サーバーを再起動

Client Islands

  1. Reactインテグレーションを追加する

    • npx astro add react
  2. src/components/Counter.tsx ファイルを作成する

    import { useState } from 'react'
    export const Counter = () => {
    const [count, setCount] = useState(0)
    const handleClick = () => {
    setCount(count + 1)
    }
    return (
    <div className="flex space-x-4 items-center">
    <div className="text-2xl">Count: {count}</div>
    <button type="button" onClick={handleClick} className="btn btn-primary">
    increment
    </button>
    </div>
    )
    }
  3. src/pages/news.astro でインポート

    ---
    import { getCollection } from 'astro:content'
    import Base from '@layouts/Base.astro'
    import { Counter } from '@components/Counter'
    const news = await getCollection('news')
    ---
    <Base>
    <Counter />
    <ul class="flex flex-col items-center space-y-2 m-auto">
    {
    news.map((x) => (
    <li class="w-full hover:bg-base-200">
    <a href={x.data.link} class="px-4 py-4 block">
    <div class="text-xl text-left">{x.data.title}</div>
    <div class="text-sm font-thin text-right">
    {x.data.pubdate?.toLocaleString()}
    </div>
    </a>
    </li>
    ))
    }
    </ul>
    </Base>
  4. カウンターが動かないことを確認

  5. <Counter/>client:load 属性を追加する

    ---
    import { getCollection } from 'astro:content'
    import Base from '@layouts/Base.astro'
    import { Counter } from '@components/Counter'
    const news = await getCollection('news')
    ---
    <Base>
    <Counter client:load />
    <ul class="flex flex-col items-center space-y-2 m-auto">
    {
    news.map((x) => (
    <li class="w-full hover:bg-base-200">
    <a href={x.data.link} class="px-4 py-4 block">
    <div class="text-xl text-left">{x.data.title}</div>
    <div class="text-sm font-thin text-right">
    {x.data.pubdate?.toLocaleString()}
    </div>
    </a>
    </li>
    ))
    }
    </ul>
    </Base>
  6. カウンターが動くことを確認する

  7. 4Gネットワークの場合コンポーネントの有り無しでどれくらい読み込み速度に差が出るか確認する

デプロイ

自鯖

Github Pages

その他のデプロイ先

astro:Env

https://docs.astro.build/en/guides/environment-variables/#type-safe-environment-variables

integrations

https://astro.build/integrations/

便利なintegration