Open32

Astroを使ったサイト制作時のメモ書き

kozarukozaru

WordPress検討

  • userへのエンドポイントアクセス制御(functions.php)
  • SiteGuard WP Pluginインストール(ログインurl / 画像認証)
  • データベースアップグレード中
  • 設定ファイル系をgit管理に変更
kozarukozaru

CloudFlare Pagesで、Astroプロジェクトのurlにリダイレクトする

CloudFlare Pagesのリダイレクト設定

https://developers.cloudflare.com/pages/configuration/redirects/

  • ファイル_redirects
  • 書き方
/test.html    /posts/22    301
/2024/07/25/当クリニックの感染症対策について/    /posts/9    301
/2024/07/25/当クリニックの%E3%80%80感染症対策について/    /posts/9    301

日本語URLの特殊文字はエンコードする

  • 全角スペース: %E3%80%80
  • 半角スペース: %20

Astroへの配置

/public/ 配下に上記のファイルを置くとgithubからのデプロイに対応可能

kozarukozaru

使用するサービスの設定

  • WordPressをサーバーに公開&インストール
  • Githubアカウント作成
  • CloudFlareアカウント作成
  • microCMSアカウント作成
  • Googleカレンダー作成
kozarukozaru

GoogleカレンダーAPIキーの取得

  1. Googleカレンダーの準備をする
    1. 設定と共通(一般公開して誰でも利用できるようにする)
    2. カレンダーのIDをメモ(カレンダーの統合カレンダー ID)
  2. GoogleカレンダーのAPIキーを取得する
    1. Google Cloud プラットフォームにアクセス
      1. プロジェクトも必要
    2. Google Calendar APIを選択して、有効にする
    3. 認証情報を作成からAPIキーを作成する
  3. APIキーに制限をつける
    1. webサイトを選択、URLを指定し、APIをCalenderAPIのみにする

参考サイト
https://www.re-d.jp/news/article/9567/

kozarukozaru

AstroをInit(必要なパッケージをインストール)

プロジェクト用ディレクトリを作成するので実行するディレクトリ階層に注意!

npm create astro@latest -- --template minimal
cd [プロジェクト用ディレクトリ名]
npm run dev

http://localhost:4321/


パッケージインストール(当時のメモ)
lint系の設定ファイルも追加した(詳細割愛)

  "dependencies": {
    "@astrojs/check": "^0.5.10",
    "@astrojs/cloudflare": "^11.1.0",
    "@astrojs/tailwind": "^5.1.0",
    "@astrojs/vue": "^4.4.0",
    "@headlessui/tailwindcss": "^0.2.1",
    "@headlessui/vue": "^1.7.22",
    "@heroicons/vue": "^2.1.5",
    "astro": "^4.15.10",
    "autoprefixer": "^10.4.20",
    "microcms-js-sdk": "^3.1.1",
    "postcss": "^8.4.41",
    "tailwindcss": "^3.4.10",
    "typescript": "^5.4.5",
    "vue": "^3.4.27"
  },
  "devDependencies": {
    "@eslint/js": "^9.11.1",
    "@fullcalendar/core": "^6.1.14",
    "@fullcalendar/daygrid": "^6.1.14",
    "@fullcalendar/google-calendar": "^6.1.14",
    "@typescript-eslint/eslint-plugin": "^8.8.0",
    "@typescript-eslint/parser": "^8.8.0",
    "add": "^2.0.6",
    "eslint": "^9.11.1",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-prettier": "^5.2.1",
    "globals": "^15.10.0",
    "prettier": "^3.3.3",
    "prettier-plugin-astro": "^0.14.1",
    "prettier-plugin-organize-imports": "^4.1.0",
    "typescript-eslint": "^8.8.0"
  }
kozarukozaru

レイアウトとメニュー作成

Layout

https://docs.astro.build/ja/basics/layouts/

レイアウトは、ページテンプレートのような再利用可能なUI構造を作成するために使用されるAstroコンポーネントです。

HeadlessUI (for Vue)

import tailwind from '@astrojs/tailwind'
import vue from '@astrojs/vue'
import { defineConfig } from 'astro/config'

// https://astro.build/config
export default defineConfig({
  integrations: [
    tailwind(),
    vue({
      script: 'module',
      devtools: true,
    }),
  ],
})
kozarukozaru

CloudFlarePagesを作ってデプロイ

以下をプロジェクトに設置

Actionのworkflowsファイル

.github/workflows/main.yml

name: 🚀 Deploy on push
on:
  push:
    branches:
      - main
jobs:
  deploy:
    runs-on: ubuntu-latest
    name: Deploy
    steps:
      - name: 🚚 Get latest code
        uses: actions/checkout@v4
      - name: Use Node.js 20
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: yarn
      - name: Install
        shell: bash
        run: yarn install
      - name: Build
        shell: bash
        run: |
          yarn run build
      - name: Publish
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CF_ACCOUNT_ID }}
          command: pages publish --project-name=${{ secrets.CF_PROJECT_NAME }} ./dist

Astro設定ファイル

astro.config.mjs

import cloudflare from '@astrojs/cloudflare'
// 省略

export default defineConfig({
  output: 'server',
  adapter: cloudflare(),
  integrations: [
    tailwind(),
    vue({
      devtools: true,
    }),
  ],
  vite: {
    define: {
      'import.meta.env.RUNTIME': JSON.stringify('cloudflare'),
    },
    ssr: {
      external: ['node:fs', 'node:path'],
    },
  },
})

Githubでの対応

以下をActionsSecretに登録する

  • CF_API_TOKEN: 作る
  • CF_ACCOUNT_ID: アカウントID
  • CF_PROJECT_NAME: プロジェクトネーム

CloudFlareでの対応

Pagesの作成ページでGithubリポジトリimportする

kozarukozaru

microCMSとの連携

  1. APIを作る

  2. .env.local にmicroCMSのドメインとAPIキーを書く
  3. importロジックを書く src/library/microcms.ts
//SDK利用準備
import { createClient, type MicroCMSQueries } from 'microcms-js-sdk'

//型定義
export type News = {
  id: string
  createdAt: string
  updatedAt: string
  publishedAt: string
  revisedAt: string
  date: string
  title: string
  content: string
  link: string
}
export type NewsResponse = {
  totalCount: number
  offset: number
  limit: number
  contents: News[]
}

//APIの呼び出し
export const getNews = async (
  clientDomain: any,
  clientKey: any,
  queries?: MicroCMSQueries,
) => {
  if ((clientDomain as string) && (clientKey as string)) {
    const client = createClient({
      serviceDomain: clientDomain as string,
      apiKey: clientKey as string,
    })
    return (await client.get<NewsResponse>({ endpoint: 'news', queries }))
      .contents
  } else {
    return []
  }
}
  1. pageでimport後表示する
---
import { getNews } from '../library/microcms'
const contents = (await getNews(
  import.meta.env.VITE_MICROCMS_DOMAIN,
  import.meta.env.VITE_MICROCMS_APIKEY,
  { fields: ['id', 'title'] },
)) as {
  id: string
  title: string
}[]
---
      <ul class="space-y-2">
        {
          contents.map((content) => (
            <li class="text-gray-600">{content.title}</li>
          ))
        }
      </ul>

環境変数

ローカル

.env.localに追加する

CloudFlarePages

シークレットにもAPI KEYとDOMAINを追加する

参考
https://zenn.dev/kazukimiyazato/articles/948b6befbda7f8

kozarukozaru

Googleカレンダーを追加

環境変数は、microCMSと同様に設定

FullCalendarのサンプルVueコンポーネント

<script setup>
import { onMounted } from 'vue';
import { Calendar } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
import googleCalendarPlugin from '@fullcalendar/google-calendar';
const props = defineProps({
  apiKey: {
    type: String,
    required: true
  },
  apiId: {
    type: String,
    required: true
  }
});
onMounted(() => {
  const calendarEl = document.getElementById('calendar');
  const calendar = new Calendar(calendarEl, {
    plugins: [googleCalendarPlugin, dayGridPlugin],
    headerToolbar: {
      left: 'prev,next today',
      center: 'title',
      right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek',
    },
    googleCalendarApiKey: props.apiKey,
    events: {
      googleCalendarId: props.apiId,
    },
  });
  calendar.render();
});
</script>


<template>
  <div id="calendar"></div>
</template>

ページへの配置

---
import FullCalendar from '../components/FullCalendar.vue'

const googleCalendarId = (import.meta.env.GOOGLE_CALENDAR_ID as string) ?? ''
const googleCalendarApiKey =
  (import.meta.env.GOOGLE_CALENDAR_API_KEY as string) ?? ''
---
    <FullCalendar
      v-if="googleCalendarApiKey && googleCalendarId"
      client:only="vue"
      apiId={googleCalendarId}
      apiKey={googleCalendarApiKey}
    />
kozarukozaru

WordPressリダイレクト設定

プラグイン:Redirect Non-Admin Users
プラグイン作成後、有効化し、トップページを固定ページに変更する

<?php
/*
Plugin Name: Redirect Non-Admin Users
Description: 未ログインまたは管理者以外のユーザーを指定したURLへリダイレクトします。
Version: 1.0
Author: ***
*/

function redirect_non_admin_users() {
    if ( ! is_user_logged_in() || ! current_user_can( 'administrator' ) ) {
        if ( is_singular( 'post' ) || is_page() ) {
            wp_redirect( 'https://takada-kodomo.com/404' );
            exit;
        }
    }
}
add_action( 'template_redirect', 'redirect_non_admin_users' );

kozarukozaru

REST API の 'users' エンドポイントへのアクセスを無効化

子テーマを作り、functions.phpに以下を設定する

functions.php

<?php
// REST API の 'users' エンドポイントへのアクセスを無効化
add_filter( 'rest_endpoints', 'disable_users_endpoint' );

function disable_users_endpoint( $endpoints ) {
    if ( isset( $endpoints['/wp/v2/users'] ) ) {
        unset( $endpoints['/wp/v2/users'] );
    }
    if ( isset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ) ) {
        unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
    }
    return $endpoints;
}
kozarukozaru

AstroでWPRestAPIをFetch

src/library/wordpress.ts

export async function getWPPosts(wpApiUrl: string): Promise<any[]> {
  try {
    const postsRes = await fetch(`${wpApiUrl}/?rest_route=/wp/v2/posts`)

    // レスポンスのステータスを確認
    if (!postsRes.ok) {
      throw new Error(`HTTP error! status: ${postsRes.status}`)
    }

    // JSONに変換
    const postsData = await postsRes.json()

    return postsData // 必要に応じて戻り値として返す
  } catch (error) {
    console.error('Error fetching posts:', error)
    return []
  }
}

importサンプル

---
import { getWPPosts } from '../library/wordpress'
const wpApiUrl = import.meta.env.WP_API_URL ?? ''
const posts = (await getWPPosts(wpApiUrl)) as any[]
---
        <ul class="space-y-2">
          {
            posts.map((post: any) => (
              <li>
                <a
                  href={`/posts/${post.id}`}
                  class="text-blue-600 hover:underline"
                >
                  <h3
                    class="text-xl font-semibold"
                    set:html={post.title.rendered}
                  />
                </a>
              </li>
            ))
          }
        </ul>

CloudFlarePagesのリダイレクト

public/_redirectsに以下を追加して確認
https://developers.cloudflare.com/pages/configuration/redirects/

/test.html    /posts/1    301
kozarukozaru

Cloud Flare Pagesのメール認証

previewデプロイ時にメール認証を入れる(productionは仮ページにする)
(登録メールアカウントでの認証が入る)

Google Calendar APIへ、 preview urlを追加

test.ymlを追加

name: 🚀 Deploy on push (preview)
on:
  push:
    branches:
      -  ***  # プレビューブランチ名を入れる、プレビューブランチへのpushでトリガー

jobs:
  deploy:
    runs-on: ubuntu-latest
    name: Deploy (preview)
    steps:
      - name: 🚚 Get latest code
        uses: actions/checkout@v4
      - name: Use Node.js 20
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: yarn
      - name: Install
        shell: bash
        run: yarn install
      - name: Build
        shell: bash
        run: |
          yarn run build
      - name: Publish preview
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CF_ACCOUNT_ID }}
          command: pages publish --project-name=${{ secrets.CF_PROJECT_NAME }} ./dist --branch=preview # プレビューデプロイ用の設定
kozarukozaru

認証系

env.d.ts

/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
interface WorkerRuntime {
  runtime: {
    waitUntil: (promise: Promise<any>) => void
    env: Env
    cf: CFRequest['cf']
    caches: typeof caches
  }
}

declare namespace App {
  interface Locals extends WorkerRuntime {}
}

認証時
.env.local

VITE_MICROCMS_DOMAIN=
VITE_MICROCMS_APIKEY=
GOOGLE_CALENDAR_ID=
GOOGLE_CALENDAR_API_KEY=
WP_API_URL=
RUNTIME_ENV=local

Astro内

const runtime = Astro.locals.runtime
const cloudFlareRuntime = import.meta.env.RUNTIME_ENV !== 'local'

const wpApiUrl =
  (cloudFlareRuntime
    ? runtime.env.WP_API_URL
    : (import.meta.env.WP_API_URL as string)) ?? ''

Github

kozarukozaru

WordPressでTailwindcssを使う

先にスタイルを作って、WordPressを合わせた方がやりやすそうなので、後に回す

kozarukozaru

FigmaのスタイルをTailwindに設定する

tailwind.config.mjs

export default {
  content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
  theme: {
    extend: {
      colors: {
        // カラー設定
        yellow: {
          500: '#fff200',
        },
      },
      fontSize: {
        // フォントサイズを追加
        xxs: ['0.625rem', { lineHeight: '0.75rem' }],
      },
      fontWeight: {
        normal: '400',
        bold: '700',
      },
    },
  },
  // StyleGuideに使う背景色のクラス名を指定
  safelist: [
    'bg-yellow-500',...
  ],
  plugins: [],
}

styleguide.astro

カラーとタイポグラフィー用のVueコンポーネントを作って作成

font

Layout.astroに游ゴシックの設定をいれた
SmartHRのデザインシステムを参考にした

    <style>
      @tailwind base;
      @font-face {
        font-family: AdjustedYuGothic;
        font-weight: 400;
        src: local('Yu Gothic Medium');
      }

      @font-face {
        font-family: AdjustedYuGothic;
        font-weight: 700;
        src: local('Yu Gothic Bold');
      }

      @layer base {
        html {
          font-family:
            AdjustedYuGothic,
            Yu Gothic,
            YuGothic,
            sans-serif;
        }
      }
    </style>
kozarukozaru

StyleGuideページをbuild時はリダイレクトさせる

public/_redirects

/styleguide    /    301
kozarukozaru

メニュー・ヘッダー・フッターの追加

  • IconはSVGファイルにした
  • ヘッダーメニューとフッターメニューのリストは一旦共有
    • メニューはheadlessUIのVueなので、一旦vueで
    • 内容精査はしていくが、スタイルは大枠できた
  • Astroでもpropsが使えることがわかった。emitも使えるか調べていく
kozarukozaru

カレンダー実装

GoogleカレンダーAPIを使う

  • 入力用スプレッドシート内のイベント名を入力して、月々の更新を行う
  • テストレビュー用に翌月をPreview表示できるようにしたい(未)

カレンダーの切替

  • HeadlessUI Switch
  • eventで取得できる内容が異なるので、更新時注意する(コメントを入れておく)

FullCalendar

テーブル表示用

TableCalendar

APIのみ使用

  • EventとConsultationに分けた
  • HeadlessUI

翌々月レビュー

子育て応援(途中)

kozarukozaru

サーバーを変更(さくらインターネットに変更)
リポジトリを変更してスタート

kozarukozaru

セキュリティーは一旦全部envにできたので、必要に応じて対応

kozarukozaru

Fullカレンダーを使う場合とAPIから値を取得して表示する場合で
型定義などが異なるので注意する

  • ConsultationとEventを分けた
    • Fullカレンダー・Tableカレンダー・トップページでそれぞれ設定がことなる
    • リファクタリングもできそうだが、今回は一旦このまま進める
  • Modal
    • Tableカレンダー・トップページは同じ構造で作成
  • スクロールリンク
    • ざっくり動くようにした
    • 細かい調整は、お知らせ作成時に対応する
kozarukozaru

WordPressのexportはすべてをexport→importで不要な投稿を削除した

kozarukozaru

indexページは、以下に分けてページング実装した
wordpress.tsに別途functionsを追加して作成

  • /staffblog/index.astro
  • /staffblog/[page].astro

子テーマでのCSSの読み込み

/**
 * 子テーマでのファイルの読み込み
 */
add_action('wp_enqueue_scripts', function() {
  wp_enqueue_style('child_style', get_stylesheet_directory_uri() .'/style.css', [], date("ymdHis", filemtime( get_stylesheet_directory() .'/assets/output.css')));
  wp_enqueue_style('my-child_style', get_stylesheet_directory_uri() .'/assets/output.css', array('child_style'), date("ymdHis", filemtime( get_stylesheet_directory() .'/assets/output.css')));
});

kozarukozaru

WordPressのTailwind設定

wp-bakで作業

  • TailwindをStandAloneで動作させる
  • tailwind.config.jsをコピー
  • index.htmlにHTMLをコピーし、StandAloneで作成したTailwindCSSを適用して、WordPressのクラスや- HTML構造に合わせたスタイルをinput.cssに作成
    • VS CodeのGo Liveで表示させる
  1. Start a watcher
./tailwindcss -i input.css -o output.css --watch
  1. Open index.html
  2. Edit input.css
  3. See the result in output.css
  4. Copy and paste the result to wordpress
  5. See the result in wordpress
  6. Repeat from 3
  7. Done and do minify
./tailwindcss -i input.css -o output.css --minify
  1. Copy and paste the minified css to wordpress
kozarukozaru

お知らせ

  • microCMS
    • news
    • emergency
      • 1つ目を表示・空にすると非表示
  • スクロールリンクを調整
    • CSS+JS:handleClickメソッドを作成
    • offsetはJSが効いている状態・時間あれば調整する
    • 11/14再度修正した
:root {
  --header-height: 58px; /* ヘッダーの実際の高さに合わせて調整してください */
}

html {
  scroll-padding-top: var(--header-height);
  scroll-behavior: smooth;
}
kozarukozaru

WordPressのクライアントスクリプト対応
・アクセス制限(URL)

/**
 * REST APIを特定のサイトにのみ許可
 */
function restrict_rest_api_access($result) {
  // WordPressの管理画面からのアクセスは許可
  if (is_admin()) {
    return $result;
  }

  // ローカル環境からのアクセスは許可
  if (isset($_SERVER['REMOTE_ADDR']) && in_array($_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])) {
    return $result;
  }

  // リクエスト元のURLを取得
  $request_origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';
  
  // User-Agentをチェック(Astroからのリクエストを許可)
  $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
  if (strpos($user_agent, 'Node.js') !== false || strpos($user_agent, 'node-fetch') !== false) {
    return $result;
  }

  // 許可するURL
  $allowed_urls = [
    '', // ここに追加
    get_site_url() // WPURL
  ];

  // Originがある場合のみチェック
  if (!empty($request_origin) && !in_array($request_origin, $allowed_urls)) {
    error_log('Forbidden access. Origin: ' . $request_origin);
    return new WP_Error('forbidden_rest_api', 
      __('REST API access is forbidden from this origin.', 'text-domain'), 
      array('status' => 403)
    );
  }

  return $result;
}

// フィルターの追加
add_filter('rest_authentication_errors', 'restrict_rest_api_access');
  • 動的ルーティングを実現するためにクエリパラムURLに変更
    • Astroではページが1ページになるため
  • リストページは20ページ分を作成して、リストがない場合はリダイレクトさせた
kozarukozaru

microCMSのクライアントスクリプト対応

  • getのみのAPIキーを作成した
  • IP制御などセキュリティーを上げるためには費用が必要