TOMOSHIBI Devhttps://dev.katari-no-tomoshibi.com開発ノウハウをシェアする技術ブログ | TOMOSHIBI DevSat, 22 Feb 2025 11:59:20 +0000jahourly1https://dev.katari-no-tomoshibi.com/wp-content/uploads/2025/02/cropped-4E5CD06B-8E09-4B80-AFCE-6C0E6FAEFB9B-32x32.webpTOMOSHIBI Devhttps://dev.katari-no-tomoshibi.com3232 WordPress REST APIにCORSを設定する方法【シンプル解説】https://dev.katari-no-tomoshibi.com/wordpress-api-cors-setup/Sat, 22 Feb 2025 11:59:18 +0000https://dev.katari-no-tomoshibi.com/?p=69

はじめに WordPress REST APIを外部のフロントエンド(例:Next.js、Vue.js)から利用する際、CORS(Cross-Origin Resource Sharing) の設定が必要になることがあり ... ]]>

はじめに

WordPress REST APIを外部のフロントエンド(例:Next.js、Vue.js)から利用する際、CORS(Cross-Origin Resource Sharing) の設定が必要になることがあります。デフォルトでは、異なるオリジン(ドメイン)からのリクエストはブロックされるため、適切に設定しないとAPIが利用できません。

本記事では、WordPressのCORSを設定する方法 をシンプルに解説します。


1. WordPressにCORSを設定する方法

1.1. functions.php にCORSを追加する

WordPressのテーマまたは子テーマの functions.php に以下のコードを追加します。

function add_cors_http_header() {
header("Access-Control-Allow-Origin: https://frontend.com"); // 許可するオリジンを指定
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
}
add_action('rest_api_init', function () {
add_action('send_headers', 'add_cors_http_header');
});

解説

  • Access-Control-Allow-Origin:許可するオリジンを指定(* にすると全オリジン許可)
  • Access-Control-Allow-Methods:許可するHTTPメソッドを指定
  • Access-Control-Allow-Headers:送信可能なヘッダーを指定(認証が必要な場合は Authorization も追加)

この設定を行うことで、指定したオリジン(例:https://frontend.com)からWordPressのAPIにアクセスできるようになります。


2. フロントエンド(Axios)でのリクエスト実装

CORSを設定したら、フロントエンドからAPIを呼び出すためのコードを実装します。

2.1. Axiosのインスタンスを作成

以下のように、Axiosの設定を行います。

import axios from "axios";

export const axiosTomoshibiInstance = axios.create({
baseURL: "https://example.com", // WordPressのURL
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
},
withCredentials: true,
});

2.2. WordPress APIから記事を取得する関数

Axiosを利用して、WordPressのREST APIから記事を取得する関数を作成します。

import { WP_REST_API_Posts } from "wp-types";
import { axiosTomoshibiInstance } from ".";

export const fetchPostsByTagId = async ({
tagId,
}: {
tagId: string | string[];
}): Promise<{ posts: WP_REST_API_Posts }> => {
const TAG_NUMBER = Array.isArray(tagId) ? tagId.join(",") : tagId;

const fetchedPosts = await axiosTomoshibiInstance.get<WP_REST_API_Posts>(
`/wp-json/wp/v2/posts?tags=${TAG_NUMBER}`
);

return { posts: fetchedPosts.data };
};

解説

  • axiosTomoshibiInstance.get() を使って wp-json/wp/v2/posts から記事を取得
  • tags=${TAG_NUMBER} で特定のタグに紐づく記事を取得
  • 取得したデータを posts として返す

3. CORSの設定をテストする方法

3.1. フロントエンドの開発環境でAPIを呼び出す

フロントエンドのコンポーネント内で、CORSが適用されたAPIを呼び出してみます。

import { useEffect, useState } from "react";
import { fetchPostsByTagId } from "./api";

export default function BlogPosts({ tagId }) {
const [posts, setPosts] = useState([]);

useEffect(() => {
fetchPostsByTagId({ tagId }).then((data) => setPosts(data.posts));
}, [tagId]);

return (
<div>
<h2>Blog Posts</h2>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title.rendered}</li>
))}
</ul>
</div>
);
}

3.2. cURLを使ってCORSを確認

ターミナルで以下のコマンドを実行し、レスポンスヘッダーを確認します。

curl -H "Origin: https://frontend.com" -I "https://example.com/wp-json/wp/v2/posts"

期待するレスポンスヘッダー:

Access-Control-Allow-Origin: https://frontend.com

これが表示されていれば、CORSの設定は正しく適用されています。


4. まとめ

WordPress REST APIにCORSを設定する方法を解説しました。functions.php に適切なコードを追加することで、特定のオリジンからのアクセスを許可できます。

設定方法メリットデメリット
functions.phpシンプルで簡単テーマ変更時に影響を受ける

セキュリティリスクを避けるために、Access-Control-Allow-Origin: * ではなく、許可するオリジンを明示的に指定 しましょう。

]]>
2025年に流行るWebサイトのアーキテクチャとは?ChatGPTが予測!https://dev.katari-no-tomoshibi.com/web-architecture-trends-2025/Mon, 17 Feb 2025 09:00:00 +0000https://dev.katari-no-tomoshibi.com/?p=66

Web技術は日々進化し、新しいトレンドが次々と登場しています。2025年にはどのようなWebサイトのアーキテクチャが主流になるのでしょうか?ChatGPTの予測をもとに、来年のWebサイト開発で注目される技術や構成を解説 ... ]]>

Web技術は日々進化し、新しいトレンドが次々と登場しています。2025年にはどのようなWebサイトのアーキテクチャが主流になるのでしょうか?ChatGPTの予測をもとに、来年のWebサイト開発で注目される技術や構成を解説していきます。


1. JAMstackの進化と普及

JAMstack(JavaScript、API、Markup) はすでに一般的なアーキテクチャですが、2025年にはより高度な形へと進化すると考えられます。特に エッジコンピューティング と組み合わせることで、より高速なサイトが実現されるでしょう。

予測されるトレンド

  • ISR(Incremental Static Regeneration) の活用増加
  • エッジキャッシュを活用したリアルタイム更新
  • ヘッドレスCMSと連携した動的な静的サイト

: Vercel、Netlify などのJAMstack向けホスティングの進化により、従来のサーバーレスよりもさらに効率的な開発が可能になる。


2. エッジコンピューティングとCDNの強化

CDN(コンテンツ・デリバリー・ネットワーク) の進化により、従来のサーバー中心の構成から エッジ処理を活用するアーキテクチャ へとシフトする流れが加速します。

予測されるトレンド

  • エッジAIを活用したパーソナライズド体験
  • WebAssembly(Wasm)によるクライアント側の処理高速化
  • DDoS対策のためのエッジセキュリティ強化

: Cloudflare、Fastly などの企業が提供する エッジファンクション を活用し、サーバー負荷を大幅に削減。


3. サーバーレスとモジュラーモノリス

サーバーレスアーキテクチャは、すでに普及していますが、2025年には「モジュラーモノリス」と組み合わせた形が増えると予測されます。

予測されるトレンド

  • FaaS(Function as a Service)の柔軟な利用
  • モジュラーモノリスによる開発コスト削減
  • データベースも分散化し、Serverless SQLが一般化

: AWS Lambda や Google Cloud Functions を活用しながら、Next.js の API Routes を組み合わせてシンプルな構成にする。


4. マイクロフロントエンド(Micro Frontend)の一般化

マイクロサービスの概念がフロントエンドにも適用され、2025年には 「マイクロフロントエンド」 の採用が増加すると予想されます。

予測されるトレンド

  • 複数チームで独立して開発可能なUI構成
  • Web Componentsによる共通UIの再利用
  • フェデレーション(Federated Modules)の進化

: React、Vue、Svelte などの異なる技術を組み合わせたWebアプリの開発。


5. AI駆動型Webサイト

AIの発展により、Webサイト自体が「自動最適化」する時代が到来すると考えられます。SEO対策やUX改善も AI駆動 で行われるでしょう。

予測されるトレンド

  • A/Bテストを自動で実行し、最適なUIを提供
  • コンテンツの自動生成とパーソナライズ
  • チャットボットや音声インターフェースの進化

: OpenAIのAPIを活用し、ユーザーの行動に応じてリアルタイムにレイアウトやコンテンツを変更するWebサイト。


6. ブロックチェーンと分散型Web(Web3)

2025年には Web3(分散型Web) の概念がより広く受け入れられ、 ブロックチェーン技術を活用したWebサイト が増えるでしょう。

予測されるトレンド

  • 分散型アイデンティティ(DID)の普及
  • NFTとWebコンテンツの連携
  • 分散型ストレージ(IPFS)の活用

: Webサイトのユーザー認証にEthereumやSolanaのウォレットを利用し、中央集権的なアカウント管理を不要にする。


まとめ

2025年に流行するWebサイトのアーキテクチャをChatGPTの予測をもとに解説しました。主なトレンドは以下の通りです。

✅ JAMstackとエッジコンピューティングの融合
✅ サーバーレス&モジュラーモノリスによる効率化
✅ マイクロフロントエンドの一般化
✅ AI駆動型の最適化Web
✅ Web3・ブロックチェーンの導入

これらの技術をうまく活用することで、2025年以降のWeb開発に適したサイトを構築できるでしょう。今後のWebトレンドを意識しながら、最適なアーキテクチャを選んでいきましょう!

]]>
【Next.js】クリップボードコピー機能を実装する方法(カスタムフック付き)https://dev.katari-no-tomoshibi.com/nextjs-clipboard-copy-hook/Fri, 14 Feb 2025 09:00:00 +0000https://dev.katari-no-tomoshibi.com/?p=62

💡 Next.jsで「コピーボタン」を実装しよう! Next.jsで「クリップボードにコピーする」機能を実装したいと考えたことはありませんか?例えば以下のような場面で活用できます。 本記事では、Next.jsで簡単にクリ ... ]]>

💡 Next.jsで「コピーボタン」を実装しよう!

Next.jsで「クリップボードにコピーする」機能を実装したいと考えたことはありませんか?
例えば以下のような場面で活用できます。

  • 招待コードやURLを簡単にコピーしたい
  • SNS共有ボタンでテキストをコピーする
  • ユーザーの利便性を向上させたい

本記事では、Next.jsで簡単にクリップボードコピーを実装できるカスタムフックを紹介します。
また、Reactのフォールバック処理付きで、モダンブラウザだけでなく古い環境にも対応しています。


🔍 クリップボードAPIとは?【Next.js & React】

「クリップボードにコピーする」処理は、navigator.clipboard.writeText() を使用するのが一般的です。
これは、HTTPS環境(セキュアコンテキスト)でのみ動作するため、ローカル環境やHTTPサイトでは動かないことがあります。

✅ クリップボードAPIの特徴

  • navigator.clipboard.writeText(text) でテキストをコピー可能
  • ただし HTTPS環境でしか動作しない
  • 一部の 古いブラウザでは未対応

そのため、**フォールバック処理(document.execCommand('copy'))**を組み込むと、より多くの環境で動作する実装が可能です。


🚀 Next.js対応!クリップボードコピー用のカスタムフック

Next.jsやReactで使い回せる、カスタムフック useClipboardCopy を作成しましょう。

import { useState } from 'react'

export const useClipboardCopy = () => {
const [copied, setCopied] = useState(false)

const handleClipboardCopy = async (text: string) => {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text)
} else {
const textArea = document.createElement('textarea')
textArea.value = text
textArea.style.position = 'fixed'
textArea.style.opacity = '0'
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
}

setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch (err) {
console.error('コピーに失敗しました:', err)
}
}

return {
copied,
handleClipboardCopy,
}
}

📌 ポイント

  • copied でコピー成功時のフィードバックを提供
  • モダンな navigator.clipboard.writeText を優先的に使用
  • フォールバックとして document.execCommand('copy') を用意

🖱 Next.jsで「コピーボタン」を作る(実装例)

作成した useClipboardCopy を使い、Next.jsでコピーボタンを作るコンポーネントを実装します。

import { useClipboardCopy } from '@/hooks/useClipboardCopy'

const CopyButton = () => {
const { copied, handleClipboardCopy } = useClipboardCopy()
const textToCopy = 'https://katari-no-tomoshibi.com/'

return (
<div className="flex items-center space-x-2">
<button
onClick={() => handleClipboardCopy(textToCopy)}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
{copied ? 'コピーしました!' : 'コピー'}
</button>
</div>
)
}

export default CopyButton

✅ ボタンの機能

  • クリックするとクリップボードにコピー
  • コピー成功時は「コピーしました!」と表示変更

🎨 さらにUIを改善!トースト通知を追加

Tailwind CSSを活用して、「コピーしました!」のトースト通知を表示するデザインにアップグレードしましょう。

import { useClipboardCopy } from '@/hooks/useClipboardCopy'

const CopyWithToast = () => {
const { copied, handleClipboardCopy } = useClipboardCopy()
const textToCopy = 'https://katari-no-tomoshibi.com/'

return (
<div className="relative">
<button
onClick={() => handleClipboardCopy(textToCopy)}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
コピー
</button>
{copied && (
<div className="absolute top-[-40px] left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-3 py-1 rounded shadow">
コピーしました!
</div>
)}
</div>
)
}

export default CopyWithToast

✅ まとめ:Next.jsでクリップボードコピーを実装する方法

本記事では、Next.jsでクリップボードにテキストをコピーする方法を紹介しました。

✔ useClipboardCopy カスタムフックを実装
✔ HTTPS環境では navigator.clipboard.writeText を使用
✔ フォールバック処理として document.execCommand('copy') を実装
✔ Tailwind CSS を活用してボタンのUIを改善

Next.jsアプリに組み込めば、URLコピーやテキスト共有がスムーズになります!
ぜひ実装して、より使いやすいWebアプリを作りましょう!🚀


📢 TOMOSHIBI Dev では、Next.jsやReactの技術記事を発信中!
他の技術記事もぜひチェックしてください 🔥

]]>
Next.js v15とCompressor.jsで画像最適化を実現する方法https://dev.katari-no-tomoshibi.com/nextjs15-compressorjs-image-optimization/Thu, 13 Feb 2025 09:00:00 +0000https://dev.katari-no-tomoshibi.com/?p=58

はじめに Webパフォーマンスの向上には、画像の最適化が欠かせません。特に、Next.js v15を用いたアプリ開発では、ページの高速化とSEOの強化が求められます。本記事では、Next.js v15とCompresso ... ]]>

はじめに

Webパフォーマンスの向上には、画像の最適化が欠かせません。特に、Next.js v15を用いたアプリ開発では、ページの高速化とSEOの強化が求められます。本記事では、Next.js v15Compressor.jsを組み合わせて、画像の圧縮と最適化を効率的に行う方法を紹介します。


Next.js v15とは

Next.js v15の特徴

Next.jsはReactベースのフレームワークで、以下のような特徴を持っています。

  • SSR(サーバーサイドレンダリング):サーバー側でレンダリングすることで、初回表示速度を向上
  • SSG(静的サイト生成):事前にHTMLを生成することで、キャッシュを活用した高速表示が可能
  • ISR(インクリメンタル静的再生成):一部のページを動的に再生成し、柔軟な運用を実現
  • APIルートの提供:サーバーレスAPIを簡単に実装
  • 画像最適化(next/image:遅延読み込み(Lazy Loading)、WebP変換、レスポンシブ対応などを自動化

特に**next/imageの画像最適化機能**と組み合わせることで、パフォーマンスの向上とSEOの強化が期待できます。


Compressor.jsとは

Compressor.jsの特徴

Compressor.jsは、クライアントサイドで画像を圧縮するための軽量ライブラリです。特徴として以下の点が挙げられます。

  • JPEG・PNG・WebPなどの圧縮対応
  • クライアントサイドで画像を非同期処理
  • 画像品質の調整が可能
  • 簡単なAPIで圧縮を実装可能

Compressor.jsを活用することで、Next.jsアプリにアップロードされた画像のサイズを最適化し、パフォーマンスを向上させることができます。


Next.js v15 × Compressor.jsで画像最適化を実装

手順 1: 必要なライブラリのインストール

Next.js v15プロジェクトにCompressor.jsを導入します。

npm install compressorjs

また、Next.jsのnext/imageを使用する場合、以下の設定も行っておきます。

// next.config.js
module.exports = {
images: {
domains: ['yourdomain.com'], // 外部画像を許可する場合
formats: ['image/webp'], // WebPフォーマットを有効化
},
};

手順 2: Compressor.jsを使用して画像を圧縮

Compressor.jsを用いて、ユーザーがアップロードした画像を圧縮する処理を実装します。

おすすめの圧縮率

一般的な用途では、画像品質(quality)を0.6~0.8の範囲に設定するのがベストです。

  • 0.8:品質を維持しつつファイルサイズを削減
  • 0.6:より軽量化を優先し、多少の画質低下を許容
  • 0.4以下:極端な軽量化が可能だが、視覚的な劣化が目立つ

以下のコードでは、品質を**0.6(60%)**に設定しています。

import { useState } from "react";
import Compressor from "compressorjs";

export default function ImageUploader() {
const [compressedImage, setCompressedImage] = useState<string | null>(null);

const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;

new Compressor(file, {
quality: 0.6, // 画像品質を60%に設定
success(result) {
const reader = new FileReader();
reader.readAsDataURL(result);
reader.onload = () => {
setCompressedImage(reader.result as string);
};
},
error(err) {
console.error("Compression error:", err);
},
});
};

return (
<div>
<input type="file" accept="image/*" onChange={handleImageUpload} />
{compressedImage && <img src={compressedImage} alt="Compressed" />}
</div>
);
}

このコードでは、inputから取得した画像をCompressor.jsで圧縮し、useStateで保持して表示するシンプルなコンポーネントを作成しました。


手順 3: Next.jsのnext/imageと組み合わせて最適化

Next.jsのnext/imageを使用すると、画像の遅延読み込み(Lazy Loading)やWebP変換が自動で行われます。

import Image from "next/image";

export default function OptimizedImage({ src }: { src: string }) {
return (
<Image src={src} width={800} height={600} alt="Optimized Image" />
);
}

これにより、最適化された画像をNext.jsの機能を活かしてレンダリングできます。


SEOの観点での最適化

画像最適化は、SEOにも大きな影響を与えます。以下のポイントを押さえておくと、検索エンジンでの評価が向上します。

1. 画像のWebP化

Next.jsのnext/imageを活用すると、WebPフォーマットへの自動変換が可能です。WebPは、JPEGやPNGよりも30%~50%圧縮率が高いため、GoogleのPageSpeed Insightsでも推奨されています。

2. 適切なalt属性を設定

alt属性には、画像の内容を簡潔に説明するテキストを記述しましょう。これは、Googleの画像検索最適化に寄与するだけでなく、アクセシビリティの向上にもつながります。

3. 適切な画像サイズを提供

next/imageのレスポンシブ対応機能を活用し、不要な高解像度画像の読み込みを防ぎます。

<Image src="/image.jpg" width={800} height={600} sizes="(max-width: 768px) 100vw, 50vw" alt="最適化された画像" />

このコードでは、画面幅に応じた適切な画像サイズを提供し、無駄な帯域消費を防ぎます。


まとめ

本記事では、Next.js v15とCompressor.jsを活用した画像最適化の方法を紹介しました。

  1. Next.js v15のnext/imageでSEOを強化
  2. Compressor.jsでクライアントサイド圧縮
  3. 画像品質は0.6~0.8が推奨
  4. SEO対策としてalt属性、WebP化、適切なサイズ指定を徹底

Next.jsのパフォーマンスを活かしつつ、Compressor.jsで軽量な画像を扱うことで、ユーザーエクスペリエンスの向上を目指しましょう。


次のステップ

  • CloudinaryなどのCDNと組み合わせた高度な画像最適化
  • TypeScriptを活用した型安全な実装
  • バックエンド(Node.jsやLaravel)での画像最適化との比較

TOMOSHIBI Devでは、今後もWeb開発に役立つ技術を紹介していきます!

]]>
Next.js App Routerでschema-dtsを活用し、SEO強化のための構造化データを実装する方法https://dev.katari-no-tomoshibi.com/nextjs-schema-dts-seo/Wed, 12 Feb 2025 09:00:00 +0000https://dev.katari-no-tomoshibi.com/?p=52

はじめに SEOを向上させるために、検索エンジンに正確な情報を伝えることは重要です。その手段として、Googleが推奨する**構造化データ(Structured Data)**を活用します。 Next.jsのApp Ro ... ]]>

はじめに

SEOを向上させるために、検索エンジンに正確な情報を伝えることは重要です。その手段として、Googleが推奨する**構造化データ(Structured Data)**を活用します。

Next.jsのApp Routerapp/ディレクトリ)環境で、型安全な構造化データを実装する方法を解説します。TypeScriptとschema-dtsを組み合わせて、Next.jsの最新アーキテクチャに適した構造化データの管理を行います。


schema-dtsとは?

schema-dtsは、Schema.orgの型定義をTypeScriptで利用できるライブラリです。これにより、型安全にJSON-LDフォーマットの構造化データを作成できます。

✅ schema-dtsを使うメリット

  • TypeScriptで型安全に構造化データを管理
  • コードの保守性が向上
  • Googleのリッチリザルト表示に対応しやすい

schema-dtsの導入手順

まず、Next.jsのプロジェクトにschema-dtsをインストールします。

npm install schema-dts

または

yarn add schema-dts

Next.js App Routerで構造化データを設定する方法

Next.jsのApp Router(app/ディレクトリ)では、サーバーコンポーネントやメタデータ管理の仕組みを活用できます。ここでは、schema-dtsを用いてSEO最適化を行います。

1. ブログ記事の構造化データを作成

Next.jsのページコンポーネントで、JSON-LDの構造化データを作成します。

構造化データの型定義

import { WithContext, Article } from "schema-dts";

export const getArticleSchema = (): WithContext<Article> => ({
"@context": "https://schema.org",
"@type": "Article",
"headline": "Next.js App Routerでschema-dtsを活用し、SEO強化のための構造化データを実装する方法",
"author": {
"@type": "Person",
"name": "TOMOSHIBI Dev"
},
"publisher": {
"@type": "Organization",
"name": "語りの灯火",
"logo": {
"@type": "ImageObject",
"url": "https://katari-no-tomoshibi.com/logo.png"
}
},
"datePublished": "2025-02-11",
"dateModified": "2025-02-11"
});

2. Next.jsのApp Routerに適用

Next.jsのApp Routerでは、メタデータをgenerateMetadataで定義できます。構造化データは<script>タグに埋め込む形で適用します。

app/blog/[slug]/page.tsx(ブログ記事ページ)

import { getArticleSchema } from "@/lib/schema";
import { Metadata } from "next";

export const generateMetadata = (): Metadata => {
return {
title: "Next.js App Routerでschema-dtsを活用し、SEO強化のための構造化データを実装する方法",
description: "Next.jsのApp Router環境でschema-dtsを活用し、構造化データを適用する方法を詳しく解説します。",
};
};

export default function BlogPost() {
const articleSchemaJson = JSON.stringify(getArticleSchema());

return (
<main>
<h1>Next.js App Routerでschema-dtsを活用し、SEO強化のための構造化データを実装する方法</h1>
<p>このページでは、schema-dtsを使った構造化データの導入方法を詳しく解説します。</p>

<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: articleSchemaJson }}
/>
</main>
);
}

schema-dtsの活用例

schema-dtsを使うと、さまざまな構造化データのスキーマを型安全に適用できます。

1. BlogPosting(ブログ記事)

Google検索で「リッチリザルト」に表示されやすくなるブログ記事の構造化データを適用します。

app/blog/[slug]/page.tsx

import { WithContext, BlogPosting } from "schema-dts";

export const getBlogSchema = (): WithContext<BlogPosting> => ({
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Next.js App Routerでschema-dtsを活用し、SEO強化のための構造化データを実装する方法",
"author": {
"@type": "Person",
"name": "TOMOSHIBI Dev"
},
"publisher": {
"@type": "Organization",
"name": "語りの灯火"
},
"datePublished": "2025-02-11",
"dateModified": "2025-02-11"
});

2. BreadcrumbList(パンくずリスト)

Google検索結果にパンくずリストを表示し、サイト構造を最適化できます。

app/components/Breadcrumb.tsx

import { WithContext, BreadcrumbList } from "schema-dts";

export const getBreadcrumbSchema = (): WithContext<BreadcrumbList> => ({
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "ホーム",
"item": "https://katari-no-tomoshibi.com/"
},
{
"@type": "ListItem",
"position": 2,
"name": "ブログ",
"item": "https://katari-no-tomoshibi.com/blog"
},
{
"@type": "ListItem",
"position": 3,
"name": "Next.js App Routerでschema-dtsを活用し、SEO強化のための構造化データを実装する方法",
"item": "https://katari-no-tomoshibi.com/blog/nextjs-schema-dts"
}
]
});

構造化データの検証方法

Googleの構造化データテストツールを使用して、正しく設定されているかを確認しましょう。

📌 Google構造化データテストツール:
https://search.google.com/test/rich-results


まとめ

✅ schema-dtsを使うと型安全に構造化データを記述できる
✅ Next.jsのApp Routerに適用し、SEO対策を強化する
✅ Googleのリッチリザルト対応で検索結果の視認性を向上

Next.jsのApp Router環境でSEOを強化したい方は、ぜひ試してみてください!

]]>
Next.js (App Router) × WordPress API でヘッドレスCMSを構築する方法【wp-types対応】https://dev.katari-no-tomoshibi.com/slug-nextjs-wordpress-api-integration/Tue, 11 Feb 2025 09:00:00 +0000https://dev.katari-no-tomoshibi.com/?p=41

1. Next.js のセットアップ まず、Next.js の App Router を利用したプロジェクトを作成し、wp-types をインストールします。 npx create-next ... ]]>

1. Next.js のセットアップ

まず、Next.js の App Router を利用したプロジェクトを作成し、wp-types をインストールします。

npx create-next-app@latest my-blog
cd my-blog
npm install axios wp-types

2. 記事一覧ページの作成 (app/page.tsx)

WordPress REST API から記事一覧を取得し、リストとして表示します。

2-1. 記事データを取得

import { WP_REST_API_Posts } from "wp-types"; // 型定義を適用
import Link from "next/link";

async function getPosts(): Promise<WP_REST_API_Posts> {
const res = await fetch("https://example.com/wp-json/wp/v2/posts?_embed", {
cache: "no-store",
});
if (!res.ok) {
throw new Error("Failed to fetch posts");
}
return res.json();
}

export default async function Home() {
const posts = await getPosts();

return (
<div>
<h1 className="text-3xl font-bold">ブログ記事一覧</h1>
<ul>
{posts.map((post) => (
<li key={post.id} className="border p-4 my-2">
<Link href={`/post/${post.id}`} className="text-blue-500">
<h2 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
</Link>
{post._embedded?.["wp:featuredmedia"] && (
<img
src={post._embedded["wp:featuredmedia"][0].source_url}
alt={post._embedded["wp:featuredmedia"][0].alt_text || "記事の画像"}
className="w-full h-auto mt-2"
/>
)}
</li>
))}
</ul>
</div>
);
}

✅ WP_REST_API_Posts を使用し、型安全にデータを取得
✅ _embedded を利用してアイキャッチ画像を取得


3. 記事詳細ページ (app/post/[id]/page.tsx)

詳細ページでは、記事の タイトル・アイキャッチ画像・本文 を表示します。

import { WP_REST_API_Post } from "wp-types";

async function getPost(id: string): Promise<WP_REST_API_Post> {
const res = await fetch(`https://example.com/wp-json/wp/v2/posts/${id}?_embed`, {
cache: "no-store",
});
if (!res.ok) {
throw new Error("Failed to fetch post");
}
return res.json();
}

export default async function PostPage({ params }: { params: { id: string } }) {
const post = await getPost(params.id);

return (
<div>
<h1 className="text-3xl font-bold" dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
{post._embedded?.["wp:featuredmedia"] && (
<img
src={post._embedded["wp:featuredmedia"][0].source_url}
alt={post._embedded["wp:featuredmedia"][0].alt_text || "記事の画像"}
className="w-full h-auto my-4"
/>
)}
<p className="text-sm text-gray-500">
投稿日: {new Date(post.date).toLocaleDateString()} | 著者: {post._embedded?.author?.[0]?.name}
</p>
<div dangerouslySetInnerHTML={{ __html: post.content.rendered }} className="prose" />
</div>
);
}

✅ 記事の詳細ページを動的に取得し、表示
✅ WP_REST_API_Post を適用し、型エラーを防止
✅ 公開日と著者情報を表示


4. ISR (Incremental Static Regeneration) の適用

最新記事を 自動で更新 できるように ISR を導入します。

async function getPosts(): Promise<WP_REST_API_Posts> {
const res = await fetch("https://example.com/wp-json/wp/v2/posts?_embed", {
next: { revalidate: 60 }, // 60秒ごとにデータを再取得
});
return res.json();
}

✅ 60秒ごとに最新の記事データを取得・再生成


5. Tailwind CSS でデザインを適用

記事一覧や詳細ページの見た目を改善するために、Tailwind CSS を適用します。

5-1. Tailwind CSS のインストール

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

5-2. Tailwind の設定 (tailwind.config.js)

/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./app/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};

5-3. グローバル CSS (app/globals.css)

@tailwind base;
@tailwind components;
@tailwind utilities;

✅ 記事リストや詳細ページに適用し、見た目を改善


6. まとめ

本記事では、Next.js (App Router) × WordPress API を活用し、以下の機能を実装しました。

  • ✅ WP_REST_API_Posts を使用し、型安全に記事を取得
  • ✅ WordPress API の _embed を活用し、アイキャッチ画像を取得
  • ✅ ISR (Incremental Static Regeneration) で動的に記事を更新
  • ✅ Tailwind CSS でデザインを整え、視認性を向上

この方法を活用すれば、SEO に強く、高速なヘッドレス WordPress サイトを構築できます!

]]>
【Laravel & TypeScript】Twitter API の文字数カウントを実装する方法(バリデーション付き)https://dev.katari-no-tomoshibi.com/twitter-character-count-validation-laravel-typescript/Mon, 10 Feb 2025 09:00:00 +0000https://dev.katari-no-tomoshibi.com/?p=38

はじめに Twitter API を使って投稿を自動化する際、ツイートの文字数カウント は避けて通れない重要なポイントです。 本記事では、Twitter の文字数カウントの仕様 を正しく理解し、PHP ... ]]>

はじめに

Twitter API を使って投稿を自動化する際、ツイートの文字数カウント は避けて通れない重要なポイントです。

本記事では、Twitter の文字数カウントの仕様 を正しく理解し、PHP(Laravel)と TypeScript で実装する方法 を詳しく解説します。

✅ Laravel の独自バリデーションを作成
✅ TypeScript でリアルタイムチェック(Zod を使用)
✅ PHPUnit で Laravel のバリデーションをテスト
✅ Jest で TypeScript のテストコードを作成


Twitter の文字数カウント仕様

Twitter では、1ツイートの最大文字数は 280文字 ですが、以下のルールに従う必要があります。

  • ✅ 通常の文字(英数字・ひらがな・カタカナ・漢字) → 1文字 = 1カウント
  • ✅ 全角記号や Emoji などの特殊文字 → 1文字 = 2カウント
  • ✅ URL(リンク) → 1つの URL は 23 文字としてカウントされる

このルールに基づき、実装を進めます。


1. PHP で Twitter 文字数カウントを実装

まずは PHP で Twitter の文字数カウントロジックを作成します。

PHP 実装コード

function countTwitterCharacters($text) {
$url_pattern = '/https?:\/\/[^\s]+/';
$url_count = preg_match_all($url_pattern, $text, $matches);

// URL を 23 文字換算
$text = preg_replace($url_pattern, '', $text);
$char_count = $url_count * 23;

// 文字の長さをカウント(全角・Emoji は 2 カウント)
foreach (mb_str_split($text) as $char) {
$char_count += (mb_strlen($char, 'UTF-8') > 1) ? 2 : 1;
}

return $char_count;
}

2. Laravel で Twitter 文字数バリデーションを実装

カスタムバリデーションの作成

php artisan make:rule TwitterTextLength

生成された app/Rules/TwitterTextLength.php に実装します。

カスタムバリデーションの実装

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class TwitterTextLength implements Rule
{
public function passes($attribute, $value)
{
return $this->countTwitterCharacters($value) <= 280;
}

public function message()
{
return 'ツイートの文字数が280文字を超えています。';
}

private function countTwitterCharacters($text)
{
$url_pattern = '/https?:\/\/[^\s]+/';
$url_count = preg_match_all($url_pattern, $text, $matches);
$text = preg_replace($url_pattern, '', $text);
$char_count = $url_count * 23;

foreach (mb_str_split($text) as $char) {
$char_count += (mb_strlen($char, 'UTF-8') > 1) ? 2 : 1;
}

return $char_count;
}
}

PHPUnit でバリデーションをテスト

use App\Rules\TwitterTextLength;
use PHPUnit\Framework\TestCase;

class TwitterTextLengthTest extends TestCase
{
public function test_valid_tweet()
{
$rule = new TwitterTextLength();
$this->assertTrue($rule->passes('tweet', 'これはテストです!'));
}

public function test_tweet_with_url()
{
$rule = new TwitterTextLength();
$this->assertTrue($rule->passes('tweet', 'https://example.com')); // 23文字としてカウント
}

public function test_tweet_exceeds_limit()
{
$rule = new TwitterTextLength();
$this->assertFalse($rule->passes('tweet', str_repeat('あ', 150))); // 300文字
}
}

3. TypeScript で Twitter 文字数カウントを実装

TypeScript 実装コード

function countTwitterCharacters(text: string): number {
const urlPattern = /https?:\/\/[^\s]+/g;
const urls = text.match(urlPattern) || [];

text = text.replace(urlPattern, '');
let charCount = urls.length * 23;

for (const char of Array.from(text)) {
charCount += char.length > 1 ? 2 : 1;
}

return charCount;
}

4. TypeScript で Zod を使ったバリデーション

Zod を使うと、TypeScript で直感的なバリデーションができます。

Zod バリデーションの実装

import { z } from "zod";

const tweetSchema = z.string().refine((text) => countTwitterCharacters(text) <= 280, {
message: "ツイートの文字数が280文字を超えています。",
});

// テスト
console.log(tweetSchema.safeParse("これはテストです! https://example.com 😀")); // 成功
console.log(tweetSchema.safeParse("あ".repeat(150))); // エラー

5. Jest で TypeScript のテスト

Jest でのテストコード

import { countTwitterCharacters } from "../src/countTwitterCharacters";

test("valid tweet", () => {
expect(countTwitterCharacters("これはテストです!")).toBe(10);
});

test("tweet with URL", () => {
expect(countTwitterCharacters("https://example.com")).toBe(23);
});

test("tweet exceeds limit", () => {
expect(countTwitterCharacters("あ".repeat(150))).toBeGreaterThan(280);
});

まとめ

Twitter API の文字数カウントを PHP(Laravel)と TypeScript(Zod) で実装し、
✅ Laravel で独自バリデーションを作成
✅ PHPUnit でテストを実施
✅ TypeScript でリアルタイムチェック(Zod を使用)
✅ Jest で TypeScript のテストを実装

バリデーションが強化され、API リクエスト前にチェック可能 になりました。

次に読むべき記事
Laravel で Twitter API を使ったスケジュール投稿を実装する方法

この実装を活用して、確実に投稿できるシステムを構築しましょう!

]]>
Next.js v15で非同期通信を最適化!axiosとfetchのキャッシュ活用法https://dev.katari-no-tomoshibi.com/nextjs-v15-fetch-axios-cache/Sun, 09 Feb 2025 06:31:43 +0000https://dev.katari-no-tomoshibi.com/?p=33

1. はじめに Next.jsでAPIを利用する際、毎回サーバーにリクエストを送ると速度が遅くなったり、APIの負荷が増えたりします。そこで、APIレスポンスをキャッシュすることで、アプリのパフォーマンスを向上させること ... ]]>

1. はじめに

Next.jsでAPIを利用する際、毎回サーバーにリクエストを送ると速度が遅くなったり、APIの負荷が増えたりします。
そこで、APIレスポンスをキャッシュすることで、アプリのパフォーマンスを向上させることができます。

この記事では、Next.js v15(App Router対応)で以下のキャッシュ手法を紹介します:

  • axios-cache-interceptorを使ったキャッシュ
  • fetch関数のキャッシュ
  • ISR(Incremental Static Regeneration)をfetch & axiosで実装

2. キャッシュの基本概念

キャッシュとは?

キャッシュとは、一度取得したデータを保存し、再度同じデータが必要なときに素早く取得できる仕組みです。

Next.js v15におけるキャッシュの種類

キャッシュ方法特徴
axios-cache-interceptorクライアントサイドでaxiosのリクエストをキャッシュ
fetch関数 + ブラウザキャッシュfetchのcacheオプションでリクエストをキャッシュ
ISR(Incremental Static Regeneration, App Router対応)サーバー側で一定時間ごとにページを再生成

3. axios-cache-interceptorでキャッシュする

3.1 インストール

まずはaxios-cache-interceptorをインストールします。

npm install axios axios-cache-interceptor

3.2 基本的な使い方

axiosのリクエストをキャッシュするには、setupCache関数を使ってaxiosインスタンスを作成します。

import axios from 'axios';
import { setupCache } from 'axios-cache-interceptor';

// キャッシュ機能付きのaxiosインスタンスを作成
const api = setupCache(axios.create());

async function fetchData() {
const response = await api.get('/api/data', {
cache: {
ttl: 60000, // 60秒間キャッシュ
},
});
console.log(response.data);
}

3.3 キャッシュを無効化する

特定のリクエストだけキャッシュを無効化したい場合は、cache: false を指定します。

await api.get('/api/data', { cache: false });

4. fetch関数でキャッシュする

4.1 fetchのcacheオプション

fetch APIには、cacheオプションを指定することでブラウザキャッシュを利用できます。

async function fetchWithCache(url: string) {
const response = await fetch(url, {
cache: 'force-cache', // ブラウザキャッシュを利用
});
return response.json();
}
cache オプション動作
no-cacheキャッシュを使わず毎回リクエスト
reloadキャッシュを無視して常に新しいデータを取得
force-cacheキャッシュがあればそれを使用
only-if-cachedネットワークを使わずキャッシュのみ

5. Next.js v15(App Router対応)のISRでキャッシュ

Next.js v15では、App Routerがデフォルトとなり、fetch()next: { revalidate }を設定することでISR(Incremental Static Regeneration)を利用できます。

5.1 fetchを使ったISR(App Router対応)

App Router (app/ ディレクトリ) を使う場合、fetch()next.revalidate を指定することでISRを適用できます。

export async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 60 }, // 60秒ごとにキャッシュを更新
});

return res.json();
}

次に、React Server Component(RSC)でこの関数を利用します。

import { getData } from '@/lib/api';

export default async function Page() {
const data = await getData();

return (
<div>
<h1>APIデータ</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}

5.2 axiosを使ったISR(App Router対応)

fetchの代わりにaxiosを使いたい場合、キャッシュを手動で管理する必要があります。
以下の方法で、Next.js v15のISR(Incremental Static Regeneration)をaxiosで実装できます。

📌 ISRでaxiosを使う方法

import axios from 'axios';

export async function getDataWithAxios() {
const res = await axios.get('https://api.example.com/data');

return res.data;
}

この関数を revalidate 付きのAPIルートに組み込みます。

import { NextResponse } from 'next/server';
import { getDataWithAxios } from '@/lib/api';

export const revalidate = 60; // 60秒ごとにキャッシュ更新

export async function GET() {
const data = await getDataWithAxios();
return NextResponse.json(data);
}

このAPIルートをRSCで呼び出すと、Next.js v15のISRとして機能します。

import { getDataWithAxios } from '@/lib/api';

export default async function Page() {
const data = await getDataWithAxios();

return (
<div>
<h1>APIデータ(axios)</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}

6. まとめ

✅ どの方法を使うべきか?

ケース最適なキャッシュ方法
クライアントサイドでAPIリクエストをキャッシュしたいaxios-cache-interceptor
ブラウザキャッシュを活用したいfetch(cache: 'force-cache')
サーバーサイドで定期的にキャッシュを更新したい(fetch版)ISR (fetch with next.revalidate)
サーバーサイドで定期的にキャッシュを更新したい(axios版)ISR (axios with API route & revalidate)

最後に

Next.js v15でAPIリクエストを効率化するには、用途に応じたキャッシュ方法を適用することが重要です。
パフォーマンス改善に役立ててみてください!

]]>
LaravelのコードフォーマットをGitHub Actionで自動化するhttps://dev.katari-no-tomoshibi.com/laravel-code-formatter-automation/Sun, 09 Feb 2025 06:17:21 +0000https://dev.katari-no-tomoshibi.com/?p=30

はじめに Laravel開発では、コードの可読性を保つために Laravel Pint を利用することが一般的です。しかし、開発メンバーが増えると、コードスタイルが統一されていないことが問題になることがあります。そこで、 ... ]]>

はじめに

Laravel開発では、コードの可読性を保つために Laravel Pint を利用することが一般的です。しかし、開発メンバーが増えると、コードスタイルが統一されていないことが問題になることがあります。
そこで、GitHub Actionを使ってコードフォーマットを自動化する方法 を紹介します。


1. GitHub Actionの設定

GitHubリポジトリの .github/workflows/linter.yml に、以下の内容を記述します。

linter.yml の内容

name: Fix Code Style

on: [push]

jobs:
format:
runs-on: ubuntu-latest

strategy:
fail-fast: true
matrix:
php: [8.4]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: json, dom, curl, libxml, mbstring
coverage: none

- name: Install Pint
run: composer global require laravel/pint

- name: Run Pint
run: pint

- name: Commit linted files
uses: stefanzweifel/git-auto-commit-action@v5

2. 設定内容の説明

上記のワークフローでは、以下のステップを実行します。

  1. リポジトリをチェックアウト
    • actions/checkout@v4 を使ってリポジトリを取得。
  2. PHP環境のセットアップ
    • shivammathur/setup-php@v2 を使い、PHP 8.4 をセットアップ。
    • Laravelでよく使う拡張機能(json, dom, curl, libxml, mbstring)を追加。
  3. Laravel Pintの実行
    • composer global require laravel/pint で Pint をインストール。
    • pint を実行し、コードをフォーマット。
  4. フォーマット後の変更を自動コミット
    • stefanzweifel/git-auto-commit-action@v5 を使い、変更されたファイルを自動コミット。

3. 実際の動作

このGitHub Actionをリポジトリに追加すると、コードをプッシュするたびに以下の処理が自動で実行されます。

  • LaravelのPHPコードは Pint でフォーマット。
  • フォーマット後の変更は自動でコミット。

これにより、開発者が手動でフォーマットする手間を削減し、チーム全体で統一されたコードスタイルを維持 できます。


4. まとめ

GitHub Actionsを活用することで、Laravelとフロントエンドのコードフォーマットを自動化できます。特に、チーム開発では コードスタイルの統一 が重要になりますので、ぜひ導入してみてください!

]]>
【Laravel 11対応】Twitter API v2でツイートをスケジュール投稿する方法【キュー活用】https://dev.katari-no-tomoshibi.com/laravel-twitter-api-v2-scheduled-posting/Sun, 09 Feb 2025 06:07:00 +0000https://dev.katari-no-tomoshibi.com/?p=26

はじめに 本記事では、Laravel 11 を使用し、Twitter API v2 に対応したスケジュール投稿の方法を紹介します。従来の statuses/update エンドポイントではなく、Twitter API v ... ]]>

はじめに

本記事では、Laravel 11 を使用し、Twitter API v2 に対応したスケジュール投稿の方法を紹介します。
従来の statuses/update エンドポイントではなく、Twitter API v2 の “tweets” エンドポイントを利用します。

使用する技術

  • Laravel 11
  • Twitter API v2
  • abraham/twitteroauth ライブラリ
  • Laravelのキューワーカー(非同期処理)

Twitter API v2のセットアップ

Twitter API v2 を使用するには、Twitter Developer Portal でアプリを作成し、
以下のキーを取得してください。

  1. API Key(Consumer Key)
  2. API Secret Key(Consumer Secret)
  3. Bearer Token
  4. Access Token
  5. Access Token Secret

🔗 Twitter APIの取得方法については、以下の記事を参考にしてください。
Twitter API v2の取得と設定方法

取得したAPIキーを .env に設定します。

TWITTER_CONSUMER_KEY=your_consumer_key
TWITTER_CONSUMER_SECRET=your_consumer_secret
TWITTER_BEARER_TOKEN=your_bearer_token
TWITTER_ACCESS_TOKEN=your_access_token
TWITTER_ACCESS_SECRET=your_access_secret

必要なパッケージのインストール

Twitter APIとの連携には abraham/twitteroauth を使用します。

composer require abraham/twitteroauth

composer.json には以下のように記載されます。

"require": {
"php": "^8.3",
"abraham/twitteroauth": "^7.0",
"laravel/framework": "^11.31"
}

Twitter APIを扱うリポジトリの作成(API v2対応)

statuses/update は API v1.1 のエンドポイントですが、API v2 では tweets エンドポイントを使用します。

app/Repositories/InitTwitterRepository.php

<?php

namespace App\Repositories;

use Abraham\TwitterOAuth\TwitterOAuth;

final class InitTwitterRepository
{
protected TwitterOAuth $connection;

/**
* Twitter API v2 に接続するためのコンストラクタ
*/
public function __construct()
{
$this->connection = new TwitterOAuth(
config('services.twitter.consumer_key'),
config('services.twitter.consumer_secret'),
config('services.twitter.access_token'),
config('services.twitter.access_secret')
);

// API v2 を使用するために Bearer Token をセット
$this->connection->setApiVersion('2');
}

/**
* Twitter API v2 でツイートを投稿する
*
* @param string $message 投稿するツイートの本文
* @return bool 成功時は true, 失敗時は false
*/
public function postTweet(string $message): bool
{
$response = $this->connection->post('tweets', [
'text' => $message
], true); // API v2 の場合は true を指定

// エラー処理
if ($this->connection->getLastHttpCode() == 201) {
return true; // 成功
} else {
\Log::error('Twitter API Error: ' . json_encode($response));
return false; // 失敗
}
}
}

💡 ポイント

  • Twitter API v2 を利用 (setApiVersion('2'))
  • tweets エンドポイントを使用
  • APIレスポンスをチェックし、成功したら true を返す

投稿処理を非同期で実行するキュージョブ

キューを使用して非同期でTwitter投稿を実行します。

app/Jobs/PostToTwitterJob.php

<?php

namespace App\Jobs;

use App\Repositories\InitTwitterRepository;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class PostToTwitterJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

protected string $message;

/**
* ジョブのコンストラクタ
*
* @param string $message 投稿するツイートの本文
*/
public function __construct(string $message)
{
$this->message = $message;
}

/**
* ジョブの実行処理(Twitterに投稿する)
*
* @param InitTwitterRepository $twitterRepository
* @return void
*/
public function handle(InitTwitterRepository $twitterRepository)
{
if (!$twitterRepository->postTweet($this->message)) {
\Log::error('Twitter投稿に失敗しました: ' . $this->message);
}
}
}

スケジュール投稿のコントローラー

ユーザーがツイートを予約投稿できるAPIエンドポイントを作成します。

app/Http/Controllers/CreateScheduledTwitterPostController.php

<?php

namespace App\Http\Controllers;

use App\Jobs\PostToTwitterJob;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

final class CreateScheduledTwitterPostController
{
/**
* ツイートの予約投稿を受け付ける
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function scheduleTweet(Request $request)
{
// バリデーション
$validator = Validator::make($request->all(), [
'message' => 'required|string|max:280', // ツイートは280文字まで
'scheduled_at' => 'required|date' // 予約時間は日付型
]);

if ($validator->fails()) {
return response()->json(['errors' => $validator->errors()], 400);
}

// 指定時間までの遅延を計算
$delay = now()->diffInSeconds($request->scheduled_at, false);
PostToTwitterJob::dispatch($request->message)->delay($delay);


// 最新のジョブIDを取得 ※同時実行が多いと他のジョブのIDを取得するリスクあり。
$jobId = DB::table('jobs')->latest('id')->value('id');
Log::info('JobId: '. $jobId);

return response()->json(['message' => 'Tweet scheduled successfully']);
}
}

キューの設定と実行

キューを利用するために、データベースドライバを設定し、マイグレーションを実行します。

php artisan queue:table
php artisan migrate

キューをリスンするには、以下のコマンドを実行します。

php artisan queue:work

CRONでキューワーカーを自動起動

予約ツイートを確実に実行するため、CRONジョブでキューを定期実行します。

* * * * * php /path-to-project/artisan schedule:run >> /dev/null 2>&1

まとめ

この記事では、Laravel 11Twitter API v2 に対応したスケジュール投稿を実装しました。
従来の API v1.1 ではなく、“tweets” エンドポイントを使用し、
より最新の方法でツイートを投稿できます。

✅ 今後の拡張

  • 画像付きツイートの投稿
  • スレッド投稿の実装
  • エラーハンドリングの強化
]]>