Dockerを使いローカルでWordPress動かす(nginx/PHP/MySQL)

2025/07/18 (金) - 09:00 Server

DockerでLAMPを構築しWordPress 6.8を動かしたときのメモ。あらかじめDockerの環境をインストールしている前提で、環境は以下の通りです。

  • nginx 1.29
  • PHP 8.3
  • MySQL 8.0

ファイル構成は以下の通り作成します。

[作業ディレクトリ]
├── docker-compose.yml
├── app
│   └── WordPressのファイル群
├── nginx
│   └── default.conf
└── php
 └── Dockerfile

appディレクトリにはWordPressの第るを用意しておきます。MySQLの基本情報(データベース名、ユーザ名、パスワードなど)やブラウザでアクセスする際のnginxのポート番号を指定します。こちらは今回8080とし、http://localhost:8080でアクセスできるようにします。

Dockerの設定

docker-compose.ymlに以下の通り記述します。

version: '3.8'
services:
 # MySQL
 mysql:
 image: mysql:8.0
 container_name: wp_mysql
 environment:
  MYSQL_ROOT_PASSWORD: root_password #MySQLのrootパスワード
  MYSQL_DATABASE: wp_db #MySQLのDBパスワード
  MYSQL_USER: wp_user #MySQLのDBユーザ
  MYSQL_PASSWORD: wp_password #MySQLのパスワード
 volumes:
  - db_data:/var/lib/mysql
 restart: unless-stopped
 # PHP(PHP-FPM)
 php:
 build: ./php
 container_name: wp_php
 volumes:
  - ./app:/var/www/html
 expose:
  - 9000
 restart: unless-stopped
 # Nginx
 nginx:
 image: nginx:latest
 container_name: wp_nginx
 volumes:
  - ./app:/var/www/html
  - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
 ## ブラウザでアクセスする際のポート番号
 ports:
  - "8080:80"
 depends_on:
  - php
 restart: unless-stopped
volumes:
 db_data:

nginxの設定

nginx/default.confでWebサーバーの設定をします。ディレクトリの設定やphp-fpmとの連携などを行います。

server {
 listen 80;
 server_name localhost;
 root /var/www/html;
 index index.php index.html index.htm;
 location / {
  try_files $uri $uri/ /index.php?$args;
 }
 location ~ \.php$ {
  # PHPコンテナ(docker-compose.ymlのサービス名)とポートを合わせる
  fastcgi_pass php:9000;
  fastcgi_index index.php;
  include fastcgi_params;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  fastcgi_param PATH_INFO $fastcgi_path_info;
 }
 location ~ /\. {
  deny all;
 }
}

PHPの設定

php/Dockerfileに以下を記述します。WordPressの動作に必要なモジュールや実行ユーザ名を設定します。

FROM php:8.3-fpm
# WordPressに必要なPHP拡張モジュール
RUN apt-get update && apt-get install -y \
 libfreetype6-dev \
 libjpeg62-turbo-dev \
 libpng-dev \
 libzip-dev \
 libonig-dev \
 && docker-php-ext-configure gd --with-freetype --with-jpeg \
 && docker-php-ext-install -j$(nproc) gd mysqli pdo_mysql zip mbstring opcache
WORKDIR /var/www/html
USER www-data

Dockerの起動

すべての作業が終わったら、作業ディレクトリに移動しDockerのコンテナを実行します。

$ cd 作業ディレクトリ(置き換えてね)
$ docker-compose up -d

正常に起動したらhttp://localhost:8080にアクセスするとインストールが起動するので、通常通りインストールします。MySQLの設定は冒頭のdocker-compose.ymlの設定を割り当てます。

WordPress動作イメージ

起動した実行例。

Dockerの停止

Dockerを停止する場合は以下のコマンドを実行します。

$ docker-compose down

WordPressの投稿内にウィジェットを配置する

2025/07/14 (月) - 09:00 Program

WordPressの投稿や固定ページの本文内に任意のウィジェットを挿入したい場合。まず、あらかじめにWordPressのfunction.phpにウィジェットの作成をし、WordPress管理画面の[外観]→[ウィジェット]からウィジェットの設定しておきます。

if(function_exists('register_sidebar')) {
 register_sidebar(array(
  'name' => 'ウィジェットのタイトル' ,
  'id' => 'ウィジェットのID', //半角英数小文字
  'description' => 'ウィジェットの説明文',
  'before_widget' => '<aside>',
  'after_widget'  => '</aside>',
  'before_title'  => '<h2>',
  'after_title'   => '</h2>'
 ));
}

さらにfunction.phpにショートコードでウィジェットを呼び出す関数を作成します。

function widget_post_func($atts) {
 $atts = shortcode_atts(array(
  'id' => '',
 ), $atts, 'widget');
 if (empty($atts['id'])) {
  return false;
 }
 ob_start();
 dynamic_sidebar($atts['id']);
 $widget_content = ob_get_clean();
 return $widget_content;
}
add_shortcode('widget', 'widget_post_func');

最後にWordPressの投稿画面の任意の位置にショートコードを埋め込みます。idの属性には設定したウィジェットのIDを入れておきます。

[widget id="ウィジェットのID"]

プレビューすると表示されると思います。ウィジェットを更新するとページや記事に埋め込んだすべてのウィジェットが一括更新されるので便利です。

ウィジェットを表示した例

例えば広告バナーやリンク集、CTAなどを設置したい場合に有効です。

OpenAI APIとNext.js(React)でGPTの回答をマークダウン(Markdown)で取得する

2025/06/02 (月) - 09:00 JavaScript

Next.js(AppRouter)とOpenAI APIを利用して、テキストフォームから入力した質問の回答をマークダウン形式で取得し、HTMLで表示するサンプル。Next.jsやReactでマークダウンを扱う場合はreact-markdownのモジュールを使うと便利です。

$ npm i react-markdown

予めモジュールを入れておきましょう。あとはOpenAI PlatformでAPIキーの発行とクレジットの確保も忘れずに。今回は以下の環境下で実装しています。

  • Next.js 14.2.x (AppRouter)
  • React 18.3.x
  • react-markdown 1.9.x

取得したAPIキーは.envで設定します。

API_KEY=sk-********************

フロント部分のpage.tsxで質問フォームと表示エリアを実装。ポイントは回答部分をReactMarkdownでマークアップすることです。これによりマークダウン形式のテキストがHTMLに変換されて表示されます。

'use client';
import { useState } from 'react';
import ReactMarkdown from 'react-markdown';
export default function Home() {
 const [prompt, setPrompt] = useState('');
 const [response, setResponse] = useState('');
 const [loading, setLoading] = useState(false);
 const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  setLoading(true);
  const res = await fetch('/api/chatgpt', {
   method: "POST",
   headers: {
    'Content-Type': 'application/json',
   },
   body: JSON.stringify({ prompt })
  });
  const data = await res.json();
  setResponse(data.text);
  setLoading(false);
 };
 return (
  <main>
   <form onSubmit={handleSubmit}>
    <textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="質問を入力してください"></textarea>
    <button type="submit" disabled={loading}>質問する</button>
   </form>
   {loading && <p>生成中…</p>}
   {response && (
    <div>
     <ReactMarkdown>{response}</ReactMarkdown>
    </div>
   )}
  </main>
 );
}

API部分の実装。Next.jsのAPI Routesを利用して実装しました。srcディレクトリに/api/chatgpt/route.tsを配置し実装します。フロントからリクエストされたされたプロンプトをPOSTで処理します。

import { NextResponse } from 'next/server';
export async function POST(req: Request) {
 try {
  const { prompt } = await req.json();
  const response = await fetch('https://api.openai.com/v1/chat/completions', {
   method: 'POST',
   headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.API_KEY}`,
   },
   body: JSON.stringify({
    model: 'gpt-4o-mini', //モデルを選択
    messages: [
     { role: 'user', content: prompt }
    ],
   }),
  });
  const data = await response.json();
  const text = data.choices[0].message?.content?.trim() || '';
  return NextResponse.json({ text });
 } catch (error) {
  console.error(API エラー:', error);
  return NextResponse.json({ error: '生成に失敗しました' }, { status: 500 });
 }
}

実際に実装し質問を実行した際の表示例。蔦屋重三郎の生い立ちについて質問し正常にHTMLフォーマットで回答文が表示されています。

回答結果

フロントのCSS側はマークダウンで再現できる一般的なHTML要素(見出し、リスト、table等)が綺麗に見やすく表示されるように整えておく必要があります。

AWS LambdaとDeepSeek APIでLINE botを作る

2025/05/16 (金) - 09:00 Others

LINEのMessaging API&AWS Lambda&DeepSeek APIを組み合わせて自動返信するLINE Botを作成しました。構成は以下とします。LINE Messaging APIからAWS LambdaからDeepSeek APIを通しユーザーと会話するフローです。

インフラ構成図

LINEのbotを作成するには以下のものが必要です。また、あらかじめDeepSeek PlatformのAPIキーを用意しておきます。

  • LINE Developers
  • LINE 公式アカウント

LINE Developersの設定

まず、LINE Developersにアクセスし、ログインし開発者アカウントを設定します。LINEアカウントのチャットボットの肝となる「プロバイダー」を作成します。プロバイダーとはLINE開発ツールでサービスを提供する個人・組織のことです。

続いてMessaging APIチャネルの作成します。

LINE公式アカウントの作成

LINE公式アカウントの作成にアクセスし、アカウントを作成します。

チャネル基本設定

  • [基本設定]でBOTの名前やアイコンなどを設定します。
  • [機能の利用]でグループ・複数人トークへの参加を有無、写真や動画の受け取りの有無を設定します。[写真や動画の受け取り]は基本OFFが良いでしょう。

Messaging APIの設定

Messaging APIの設定はLINE Developersのコンソールから設定します。作ったチャンネルのアイコンをクリックし、[Messaging API設定]の[Webhook設定]からWebhook URLとWebhookの利用の有無を設定。

  • [応答機能]のチャットを無効にする
  • [Webhook]を有効にし、Messaging APIの設定を開く(後述)
  • [応答機能]の応答メッセージを無効にする
  • ページ下部にある[チャネルアクセストークン(長期)]を発行し控えておきます

AWS Lambdaの設定

AWS Lambdaにアクセスし新規で関数を作成します。[関数名]は任意の名前に設定し、[ランタイム]は[Python 3.x]を選択します。[アーキテクチャ]は[x86_64]のままにし[関数を作成]を実行します。

次に関数の設定に移動し、[環境変数]であらかじめ控えておいたDeepSeekのAPIキーと、LINEのチャネルアクセストークンを設定しておきます。今回はそれぞれDEEPSEEK_APIKEYLINE_ACCESS_TOKENという名前でキーを設定しました。

AWS Lambdaの環境変数設定

次に[コード]に移動し、Python 3.xとしてコードを実装します。コードの例は下記の通り。またPythonでAPI通信をするためにはrequestsモジュールが必要なのですが、Lambdaでは使えないので適宜レイヤーで追加しておきます。

コードの例

import json
import os
import requests
LINE_ACCESS_TOKEN = os.getenv('LINE_ACCESS_TOKEN')
DEEPSEEK_API_KEY = os.getenv('DEEPSEEK_APIKEY')
LINE_REPLY_API_URL = 'https://api.line.me/v2/bot/message/reply'
DEEPSEEK_API_URL = 'https://api.deepseek.com/chat/completions'
def lambda_handler(event, context):
 body = json.loads(event['body'])
 for message_event in body.get('events', []):
  if message_event['type'] != 'message' or message_event['message']['type'] != 'text':
   continue
  user_message = message_event['message']['text']
  reply_token = message_event['replyToken']
  gpt_reply = ask_gpt(user_message)
  reply_to_line(reply_token, gpt_reply)
 return {
  "statusCode": 200,
  "body": json.dumps('OK')
 }
def ask_gpt(user_input):
 headers = {
  "Content-Type": "application/json",
  "Authorization": f"Bearer {DEEPSEEK_API_KEY}"
 }
 data = {
  "model": "deepseek-chat",
  "messages": [
   {"role": "system", "content": """* 日本語で返答してください
* あなたはユーザーのアシスタントです
* ユーザーのことを「センパイ」と呼称します
* ユーザーを慕う後輩のようなキャラクターです
* ユーザーに好意を持ち敬語で話します"""},
   {"role": "user", "content": user_input}
  ]
 }
 response = requests.post(DEEPSEEK_API_URL, headers=headers, json=data)
 response_data = response.json()
 if 'choices' in response_data:
  return response_data['choices'][0]['message']['content']
 else:
  return '現在応答できません'
def reply_to_line(reply_token, message_text):
 headers = {
  "Content-Type": "application/json",
  "Authorization": f"Bearer {LINE_ACCESS_TOKEN}"
 }
 data = {
  "replyToken": reply_token,
  "messages": [
   {
    "type": "text",
    "text": message_text
   }
  ]
 }
 requests.post(LINE_REPLY_API_URL, headers=headers, json=data)

コードを保存してデプロイしたら、Lambdaの[関数の概要]から[関数 URL]をコピーしておきます。

Messaging APIの設定

再びLINE DevelopersのコンソールからMessaging APIの設定にアクセスし、[Webhook URL]に先程作ったLambdaの[関数 URL]のエンドポイントを入力し[更新]を押下します。その後[検証]を押下し正常にレスポンス(200)が返ってくることを確認します。

Webhook URLの設定

同じ設定画面にあるQRコードをスマホで読み込み、LINEの友達に追加します。レッツトーク!

LINEトークのサンプル

にゃるぴにも優秀な後輩アシスタントができました。今回は安価なDeepSeek APIで実装しましたがもちろんGPTのOpenAIのAPIでも構いません。

Messaging APIのプランと値段

Messaging APIはプランによって送信料が変わります。今回は自分用なので無料のコミュニケーションプランで作成しました。Messaging APIの料金は以下ページに掲載されています。

さくらのレンタルサーバーのphpMyAdminで巨大なSQLファイルをインポートできない時

2025/05/09 (金) - 09:00 Server

さくらのレンタルサーバーのスタンダードプラン以上では標準でMySQLが搭載されており、簡単にデータベースを扱えます。それに伴いデータベース管理ツールのphpMyAdminも装備されておりGUIでデータベース操作が行え、外部からSQLのファイルをインポートできます。しかしさくらのレンサバ標準のphpMyAdminでインポートできるファイルの上限は32MBまでのようです。

数百MBあるような巨大なSQLファイルを読み込ませることができません。SQLファイルをdata.sql.zipのようなファイルの命名規則でzipで圧縮し、32MB以下まで落としてアップもできますが、展開とインポートに時間がかかってタイムアウトすることがあります。そこでどうするか?考えました。

phpMyAdminのインポート画面

自前でphpMyAdminをインストールする

借りているサーバーに自分でphpMyAdminをインストールし、尚且つphp.iniなどでupload_max_filesizeの値を256MBなどに引き上げて、SQLファイルをアップロードできるようにします。

インポートには時間がかかるので、set_time_limitで実行時間を引き上げたりmemory_limitでメモリの上限を上げる必要もあります。ただしあくまで共用のレンタルサーバのためあまり高負荷をかけ続けるとアカウントの利用制限などに抵触する可能性もあるため注意が必要です。

SSHでサーバに接続してSQLファイルをインポートする

一部サーバーコマンドやSQLコマンドの知識が必要ですがこれが一番確実です。予めインポートしたいSQLファイルをレンタルサーバの任意の場所にアップロードしてきます。次にさくらのレンタルサーバにSSHで接続します。ログインパスワードはFTPと同じパスワードです。

ssh ユーザー名@ユーザー名.sakura.ne.jp

ログインしたらMySQLにログインします。予めさくらのレンタルサーバのコントロールパネルよりデータベースとユーザ名を作成しておきます。レンタルサーバーのコントロールパネルからデータベースの収容サーバ名(mysql****.db.sakura.ne.jp)も控えておきます。パスワードはデータベースを発行したときに設定したデータベースのパスワードです。

mysql -u データベースユーザー名 -h mysql****.db.sakura.ne.jp -p

ログインしたらデータベースを選択します。show databasesでデータベースがあることを確認し、useコマンドで使用するデータベースを選択します。

show databases;
〜データベース一覧が表示される〜
use 使用するデータベース名;

先ほどアップロードしたSQLファイルをインポートします。データベースの構造にもよりますが、数万レコードあるSQLでも1分もかからず終わるはずです。

source /home/ユーザー名/www/data.sql

インポートが終わると入力待ち状態になるので、MySQLを終了します。

exit;

さきほどレンタルサーバにアップロードしたSQLファイルは不要なので削除しておきます。

rm /home/ユーザー名/www/data.sql

さらにサーバからログアウトします。

exit

この方法のがおそらくインポートを失敗せずに行けると思います。

AWS CloudFrontで特定ファイルのみキャッシュを無効にする

2025/05/02 (金) - 09:00 Server

CloudFrontでウェブサイトを配信するとき、特定のファイルのみのキャッシュを無効化したいときの設定方法。例えば頻繁に内容が変わるHTMLファイルはキャッシュせず、更新頻度が少なかったり重いデータの画像やCSS・JavaScriptなどはCloudFrontでキャッシュさせたいときなど。

AWS CloudFrontの設定

今回は例として、HTMLファイルだけキャッシュの除外をする設定とします。

AWS CloudFrontから該当のディストリビューションを選択し、[ビヘイビア]に移動。[ビヘイビアを作成]を選択し新規作成します。[パスパターン]に*.htmlと入力。[オリジンとオリジングループ]でオリジンを選択してから、他のオプションは以下のように設定。

CloudFront Behavior

* オブジェクトを自動的に圧縮:Yes
* ビューワープロトコルポリシー:Redirect HTTP to HTTPS
* 許可された HTTP メソッド:GET, HEAD
* ビューワーのアクセスを制限する:No
* キャッシュキーとオリジンリクエスト:Legacy cache settings
* ヘッダー:なし
* クエリ文字列:すべて
* cookie:すべて
* オブジェクトキャッシュ:Customize
* 最小 TTL:0
* 最大 TTL:0
* デフォルト TTL:0

すべてのTTLを0にすることで、キャッシュされずに読み込まれます。上記を設定したら保存します。

設定の反映の確認方法

Webブラウザのネットワークヘッダーで確認できます。ChromeのWeb開発者ツールであれば[Network]からファイルを選択しレスポンスヘッダーのX-Cacheから確認できます。

X-Cache:Hit from cloudfront

CloudFrontのキャッシュになっている場合は、X-CacheHit from cloudfrontになります。今回の場合CSSや画像ファイルはキャッシュされます。

X-Cache:Miss from cloudfront

一方キャッシュされていない場合はX-CacheMiss from cloudfrontの値になります。これはキャッシュされずにオリジンから呼び出しているものになります。

注意点としては、該当のファイルはキャッシュされないためオリジンに負荷がかかるということです。S3やEC2から呼び出す場合は転送量やプロセッサの振る舞いに注意しましょう。

Claude APIでWordPressの記事を翻訳する

2025/04/28 (月) - 09:00 Program

今回はWordPressの記事タイトルと本文を生成AIを組み合わせて自動的に機械翻訳する機能を簡易的に実装しました。記事詳細ページに「英訳を見る」ボタンを配置し、押下したら英訳ページに遷移しClaude APIを利用し翻訳します。

ブラウザの機能やGoogleなどの翻訳サービスで簡単かつ無料で全文翻訳出来るので不要と言えば不要ですがネタ的に。

実装ポイントとしては以下。

  • 翻訳に時間とAPI使用料金が取られるので初回のみ翻訳
  • 2回目以降は翻訳内容をキャッシュして表示
  • 翻訳ページへの切り替えはクエリパラメータで判断
  • アクセス解析の時にパラメータの有無で翻訳ページのアクセス数を判断できる

翻訳前のブログ記事

翻訳前のブログ記事の表示(日本語)。デメリットは以下の通り

  • 翻訳内容の精度は元の文章の品質とAIモデルに依存する
  • あまり長い文章は非対応(max-tokenを超えると文章が途中で切れる)
  • APIの使用利用料金に注意

あらかじめAnthropic ConsoleでAPIキーの取得と課金は済ませておきましょう。

翻訳後のブログ記事

翻訳後のブログ記事の表示(英語)。環境は以下の通り

  • WordPress 6.5
  • PHP 8.2

細かいエラーやセキュリティ処理はしていないので、参考までに。また一部マークアップが崩れる可能性もあるのでその点も注意しましょう。

コード例

function.phpに以下を記述。指定したクエリパラメータの記事詳細にアクセスすると初回翻訳が実行されます。1度翻訳した内容はカスタムフィールドに格納するため、2回以降はカスタムフィールドの翻訳内容を表示します。

function translationContent(){
 $API = 'https://api.anthropic.com/v1/messages';
 $API_KEY = '取得したAPIキーを設定';
 if( get_post_meta(get_the_ID(), 'translation_en') ) {
  //翻訳後ならキャッシュを表示
  $the_content = get_post_meta(get_the_ID(),'translation_en',true);
  return $the_content;
 }
 //翻訳前なら翻訳を実行
 $system = '指定したテキストを英語で翻訳する。HTMLタグは変えずにテキスト内容だけ翻訳する。';
 $single_title = get_the_title();
 $single_date = get_the_time('Y/m/d (D) - H:i');
 $single_content = get_the_content();
 $content = "<h1>{$single_title}</h1><p><time>{$single_date}</time></p>{$single_content}";
 $data = json_encode([
  'model' => 'claude-3-5-haiku-20241022', //モデルを選択
  'max_tokens' => 8192, //最大トークン(モデルに依存)
  'system' => $system,
  'messages' => [
   ['role' => 'user', 'content' => $content]
  ]
 ]);
 $ch = curl_init($API);
 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
 curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'Content-Type: application/json',
  'anthropic-version: 2023-06-01',
  'x-api-key: '.$API_KEY
 ]);  
 $result = curl_exec($ch);
 $httpcode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
 curl_close($ch);
 $output = json_decode($result, true);
 if($httpcode===200){
  $contents = $output['content'][0]['text'];
  update_post_meta(get_the_ID(), 'translation_en', $contents );
  return $contents;
 }else{
  return 'エラー: '.$result;
 }
}

single.phpの任意の位置に以下を記述。クエリパラメータの有無で本来の日本語の記事と、翻訳の記事を分岐表示します。このとき英訳のページへの同線を設置します。

<?php
if($_GET['lang']==='en'){
 echo translationContent();
}else{
?>
 <h1><?php the_title(); ?></h1>
 <p><time><?php the_time('Y/m/d (D) - H:i') ?></time></p>
 <p><a href="<?php the_permalink() ?>?lang=en">この記事を英語で見る(English)</a></p>
<?php
 the_content();
}
?>

もし翻訳内容を変えたい場合はカスタムフィールドを直接変更します。また翻訳内容をリセットして再翻訳したい場合はカスタムフィールドを削除して保存した後、再度翻訳をします。

本格的に翻訳記事を用意したり、機密情報を扱うのであれば専門の翻訳業者に依頼し、WordPressの多言語プラグインを利用するのが最適です。しかし生成AIの翻訳精度もかなりの物ですね。

[GPT4o,DALL-E 3,PHP] 指定画像と似たような画像を自動生成する

2025/04/11 (金) - 09:00 Program

GPT4oで画像の解析や生成がパワーアップして、かなり自然なイラストや画像が出来るようになって「この写真の人物をジブリ風にして!」みたいなことがさらに簡単にできるようになりました。今回はGPTで指定した画像を解析し、さらにその画像に近い画像をDALL-E 3で生成できるようなものをPHPで作ってみました。

猫の生成イメージ

まずchatGPTのAPIのGPT4oのモデルを使って指定した画像の分析を行い、解析結果をもとに生成プロンプトを出力させます。次にAPIのDALL-Eのモデルを使ってプロンプトを元に画像を生成します。

<?php
define('API_KEY', '{GPTのAPIキー}');
define('API_CHAT', 'https://api.openai.com/v1/chat/completions');
define('API_IMG', 'https://api.openai.com/v1/images/generations');
$base_url = '{画像のURL}';
$image_url = '';
function analyzeImage($base_url) {
 $imageData = file_get_contents($base_url);
 $base64Image = base64_encode($imageData);
 $dataUrl = 'data:image/jpeg;base64,' . $base64Image; //画像の形式によって変更
 $headers = [
  'Content-Type: application/json',
  'Authorization: Bearer ' . API_KEY,
 ];
 $data = [
  'model' => 'gpt-4o-mini',
  'messages' => [
   [
    'role' => 'user',
    'content' => [
     [
      'type' => 'text',
      'text' => 'この画像と同じ画像を生成したいので、プロンプトを生成してください'
     ],
     [
      'type' => 'image_url',
      'image_url' => ['url' => $dataUrl]
     ]
    ]
   ]
  ],
  'max_tokens' => 1000,
 ];
 $ch = curl_init(API_CHAT);
 curl_setopt($ch, CURLOPT_POST, true);
 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 $response = curl_exec($ch);
 $responseCode = curl_getinfo($ch,CURLINFO_RESPONSE_CODE);
 curl_close($ch);
 if($responseCode === 200){
  $responseBody = json_decode($response, true);
  $responseMessage = $responseBody['choices'][0]['message']['content'];
  imageGenerator($responseMessage,$base_url);
 }
}
function imageGenerator($prompt,$base_url){
 $headers = [
  'Content-Type: application/json',
  'Authorization: Bearer ' . API_KEY,
 ];
 $data = [
  'model' => 'dall-e-3',
  'prompt' => $prompt,
  'n' => 1, //生成する画像の個数
  'size' => '1024x1024',
 ]
 $ch = curl_init(API_IMG);
 curl_setopt($ch, CURLOPT_POST, true);
 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 $response = curl_exec($ch);
 $responseCode = curl_getinfo($ch,CURLINFO_RESPONSE_CODE);
 curl_close($ch);
 if($responseCode === 200){
  $result = json_decode($response, true);
  $image_url = $result['data'][0]['url'];
  echo '<p>元画像:<img src="'.$base_url.'" alt="" loading="lazy"></p>';
  echo '<p>生成画像:<img src="'.$image_url.'" alt="" loading="lazy"></p>';
 }
}
analyzeImage($base_url);
?>

画像の解析はURL指定だとうまく行かなかったので、base64にエンコードして指定しました。

コードは汚いけど上記のような感じです。実行すると画像の解析と画像の生成によりすこし待たされますし課金もされるので、生成する個数は少ない方が良いかもしれないです。また正常に解析や生成できなかった場合のエラー処理なども省いています。

人物の生成イメージ

結果については元画像の内容やガチャのようなところもあるので、実際はプロンプトの出力まで行ってからimageFXやStable Diffusionで納得いくまで生成を試した方が効率的かもしれません。

Next.js[App Router]&microCMSでRSSフィードを実装する

2025/03/31 (月) - 09:00 JavaScript

microCMSとNext.js[App Router]でRSSを実装してみる。RSSはブログやニュース配信サイトでは必ず実装しておくべきものです。今回は以下の環境で実装します。

  • Next.js 14.2.x
  • microcms-js-sdk 3.1.x

.envで、サイト名、サイトの説明文、サイトのドメイン、microCMSのAPIキーを設定をしておきます。

SITE_URL=https://example.com
SITE_NAME=ゆかちぃのブログ
SITE_DISCRIPTION=ゆかちぃの日々を書き残すブログです♪
API_KEY={APIキー}

src/feed/route.ts に以下のように実装。タイトル、パーマリンク、サマリー、公開日の最低限の要素のみ実装しています。

APIスキーマは以下を想定します。

  • 記事タイトル:title
  • 本文:content
  • パーマリンク:id
  • 公開日:publishedAt
import { NextResponse } from 'next/server';
import { client } from '../libs/Client.js';
import { format } from 'date-fns';
export async function GET() {
const posts = await client.get({
 endpoint: 'post',
});
 const rss = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
 <channel>
  <title>${process.env.SITE_NAME}</title>
  <link>${process.env.SITE_URL}</link>
  <description>${process.env.SITE_DISCRIPTION}</description>
  ${posts.contents.map((post) => `
  <item>
   <title>${sanitizeStr(post.title)}</title>
   <link>${process.env.SITE_URL}/${post.id}</link>
   <description><![CDATA[${post.content}]]></description>
   <pubDate>${format(new Date(post.publishedAt), 'EEE, dd MMM yyyy HH:mm:ss xx')}</pubDate>
  </item>`).join('')}
 </channel>
</rss>`;
 return new NextResponse(rss, {
  headers: {
   'Content-Type': 'application/rss+xml',
  },
 });
}
function sanitizeStr(str: string): string {
 return String(str).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&apos;").replace(/"/g,"&quot;")
}

また、出力したRSSをすべてのページのhead要素内に設定し、このサイトではRSS配信しているよ!ということを認識させましょう。

<link rel="alternate" type="application/rss+xml" href="https://example.com/feed/" />

[Next.js+microCMS] 1ページ内でカテゴリごと記事一覧を表示

2025/03/21 (金) - 09:00 JavaScriptProgram

Jamstackの開発が活発になり、ヘッドレスCMSとして和製のmicroCMSを採用するケースもあるかと思います。今回はmicroCMSとNext.jsを使って記事カテゴリごとに記事一覧を分割して表示させる方法をメモします。

Next.js+microCMS 作成イメージ

記事カテゴリAPIと記事APIの2つを使います。記事のAPIスキーマにはタイトルや記事本文のほか、カテゴリ用のフィールドを用意しコンテンツ参照で記事カテゴリAPIを参照します。また開発にはmicrocms-js-sdkを使っています。今回は社員のスタッフリストをつくりました。役職ごとカテゴリを作りその下に所属するスタッフを一覧で表示します。

今回は下記の環境にて作成。

  • Node.js 20.18.x
  • Next.js 14.2.x
  • microcms-js-sdk 3.1.x

microCMSカテゴリ管理

スタッフリスト部分はコンポーネントとしてimportします。スタッフ一覧ページのファイルを/staff/page.jsとします。

import { client } from '@/libs/client';
import StaffList from '@/component/StaffList';
export default async function Home() {
 const category = await client.get({
 endpoint: 'staff_category',
 });
 return (
 <div>
 {category.contents.map((category) => (
  <article key={category.id}>
   <h2>{category.name}一覧</h2>
   <StaffList id={category.id} />
  </article>
 ))}
 </div>
 );
}

スタッフ一覧コンポーネントとして/component/StaffList.js用意します。

import Link from 'next/link';
import { client } from '@/libs/client';
import { format } from 'date-fns';
export default async function StaffList({id}){
 const category_staff = await client.get({
 endpoint: 'post',
 queries: { filters: `category[equals]${id}`}
 });
 return (
 <ul>
 {category_staff.contents.map((staff_data) => (
  <li key={staff_data.id}>
  <Link href={`/staff/${staff_data.id}`}>
  <img src={staff_data.eyecatch.url} alt="" loading="lazy" width="100" height="100" />
   <h3>{staff_data.title}</h3>
   <p><time>入社日:{format(new Date(staff_data.date), 'yyyy.MM')}</time></p>
  </Link>
  </li>
 ))}
 </ul>
 );
}

スタッフ詳細ページとして/staff/[id]/page.js用意します。

import { client } from '@/libs/client';
import { format } from 'date-fns';
export async function generateStaticParams() {
 const data = await client.get({
  endpoint: 'post',
 });
 return data.contents.map((data) => {
  return { id: data.id}
 })
}
export default async function DetailPage( props ) {
 const data = await client.get({
  endpoint: `post/${props.params.id}`,
 });
 return (
  <div>
   <p>{data.category.name}</p>
   <h1>{data.title}</h1>
   <img src={data.eyecatch.url} alt="" loading="lazy" width="100" height="100" />
   <p><time>入社日:{format(new Date(data.date), 'yyyy.MM')}</time></p>
   <div dangerouslySetInnerHTML={{ __html: data.content }}></div>
  </div>
 );
}

所属部署(カテゴリ)、名前、顔写真、入社日、自己紹介メッセージが表示されました。

詳細画面イメージ

コードの細かい内容についてはそれぞれのCMSに合わせてください。ところで、microCMSって2025年6月10日から値上がりするんですよね。むむー…

ページの先頭へ