Next.js[App Router]とPokeAPIでポケモンを番号で検索する。

2024/04/09 (火) - 00:00 JavaScript

前回テストでNext.jsでパスの取得方法を試してみたので、今回はそれとPokeAPIのテストを応用して、ポケモン番号でポケモンを探せるやつを作りました。

検索画面ではポケモンの番号を入れる検索のテキストボックスと検索実行ボタンのみ配置。

キルリア

ポケモン検索ボックスのあるpage.jsのコード。

"use client"
import { useState } from 'react';
import { useRouter } from 'next/navigation'
function PokeAPIHome() {
 const [number,setNumber] = useState(null);
 const router = useRouter();
 const buttonGO = (number) =>{
  router.push(number);
 }
 const onChange = (e) =>{
  setNumber(e.target.value);
 }
 return (
  <div>
   <form>
    <p>ポケモンの番号を入れてね</p>
    <p><input type="number" min="1" value={number} onChange={ onChange } placeholder="151" />
    <button type="button" onClick={ () => buttonGO( number ) }>ゲットだぜ!!</button></p>
   </form>
  </div>
 );
}
export default PokeAPIHome;

表示結果の[number]/page.jsのコード。

検索ボタンを押下すると、/ポケモン番号/というパスで遷移させる想定です。例えば入力画面で151と記入したら/151/というパスに遷移して、ミュウが表示れる…というもの。

import { notFound } from 'next/navigation'
async function PokeAPIDetail({ params }) {
 const number = params.number;
 const pokeAPI = `https://pokeapi.co/api/v2/pokemon/${number}`;
 const getAPI = async(url) => {
  const res = await fetch(url);
  if (res.ok) {
   return res.json();
  }else{
   notFound();
  }
 }
 const getPokemonName = async(data) => {
  const PokemonName = data.names.find((val) => val.language.name === "ja");
  return PokemonName.name;
 }
 const getPokemonType = async(data) => {
  let typeText = '';
  typeText = data.map( async(t,i) => {
   const getTypes = await getAPI(t.type.url);
   const getType = getTypes.names.find((val) => val.language.name === "ja");
   if(i>0){
    return '/'+getType.name;
   }else{
    return getType.name;
   }
  });
  return typeText;
 }
 const dataAPI = await getAPI(pokeAPI);
 const IMG = dataAPI.sprites.other['official-artwork'].front_default;
 const Name = await getAPI(dataAPI.species.url);
 const Type = getPokemonType(dataAPI.types).then(res => {
  return res;
 });
 const NameJA = getPokemonName(Name).then(res => {
  return res;
 });
 return (
  <div>
   <p>No.{number}</p>
   <p><img src={IMG} alt={NameJA} width="250" height="250" /></p>
   <h2>{NameJA}</h2>
   <p>{Type}</p>
  </div>
 );
}
export default PokeAPIDetail;

実行結果。281と検索するとキルリアが表示されました。

キルリア

なお、前のコードを流用して突貫で作成したのでエラー処理はそこまで作り込まず。もしAPIでエラーが発生したら404エラーとして遷移するだけです。

めのまえが まっくらに なった…!

node.jsが動くサーバにデプロイして正常に動くことは確認しましたが、検索周りの処理の作り込みがまだ甘いので改良の余地あり。

CSSだけで簡易的にimg要素の画像をダウンロード禁止にさせる

2024/03/28 (木) - 00:00 CSSHTML

CSSだけで簡易的にimg要素の画像をダウンロード禁止にさせる方法。HTTPの通信の都合上、ダウンロードは完全には防げないのであくまで参考程度に。

以下のHTMLがあったとします。

<div class="picture">
<img src="sample.jpg" width="600" height="400" alt="ねこです よろしくお願いします" />
</div>

そのうえでCSSでimg要素に対しクリッカブルとマウスのドラッグを無効にさせ、さらに念のため疑似要素タグで画像要素の上に架空要素を配置してクリックを抑制させています。

.picture{
 display: block;
 position: relative;
 width: 100%;
 max-width: 400px;
}

.picture img{
 display: block;
 position: relative;
 z-index: 1;
 width: 100%;
 height: auto;

 /*マウスのクリック抑制*/
 pointer-events: none;

 /*スマホの長押し抑制*/
 -webkit-touch-callout: none;
 -webkit-user-select: none;
 -moz-user-select: none;
 -ms-user-select: none;
 user-select: none;
 -webkit-user-drag: none;
 user-drag: none;
}

/* 架空要素を画像の上に被せて右クリック無効化 */
.picture::before{
 display: block;
 position: absolute;
 content: "";
 top: 0;
 bottom: 0;
 z-index: 2;
 width: 100%;
 /* Chromeでは何かしら透明色以外の背景色を指定ないとだめ */
 background: white;
 /* 透明度を0 */
 opacity: 0;
}

なおHTMLコードで直に画像ソースを見られたり、Webデベロッパーツールでimg要素や画像ソースを取得されたり、スクリーンショットを取られたりする等は防げません。これらはJavaScriptを駆使したところでどうやっても防げないのであくまで一時対処のみ。

エックスサーバーのレンタルサーバでNext.js起動してみた

2024/03/02 (土) - 00:00 JavaScriptServer

エックスサーバーのレンタルサーバでもnode.jsが使えると聞いたのでテストしました。2024年1月現在に検証、自己責任にて。

環境は以下。

  • エックスサーバー レンタルサーバ(スタンダートプラン)
  • Node.js v21
  • Next.js v14.1

レンタルサーバのsshにログインしてNode.jsのバージョンを確認。

$ node -v
v21.6.1

HTTPサーバとしてNode.jsを使用した際デフォルトでは3000番ポートが使用されます。ブラウザでアクセスした時に3000番ポートを表示できるようにするため、webサーバーにリバースプロキシを設定します。そこで対象ディレクトリの.htaccessに次の記述をします。

DirectoryIndex disabled
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule (.*) http://localhost:3000/$1 [P,L,QSA]
</IfModule>

buildしたファイル一式をサーバーにアップし、コマンドラインでアプリを起動します。

$ npm run start
> my-app@0.1.0 start
> next start

ブラウザで自分のレンタルサーバにアクセスすると表示されます。なお、共用領域であるレンタルサーバーではNode.jsをHTTPサーバーとして常駐することは推奨されていないので、確認したらサーバーを落としてプロセスを切っておきましょう。

Next.jsを起動した例

デーモンで常にNode.jsを起動させておくのであれば、素直にVPSや専用サーバに環境を構築すべきです。

SWRで手軽にデータ取得

2024/02/09 (金) - 00:00 JavaScript

ReactやNext.jsでデータ取得をより簡単に実現できるライブラリ、SWRを使ってみました。

これまではfetch APIuseStateuseEffectを扱って処理していたため、コードも長くなっていましたがより簡潔かつ可読性も上がるのが特徴です。またキャッシュ対策やリクエストの重複排除なども備えているので、ユーザにも優しく高速である点もポイントかと思います。

まず、インストールですがnpmでのインストール方法は以下のようにコマンドをタイプします。

$ npm install swr

公式のドキュメントを参考にコードを書いてみました。前回Reactの勉強で使ったお天気のAPIを取得してみます。

'use client';
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(res => res.json());
export default function page() {
 const { data, isError, isLoading } = useSWR('https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json', fetcher)
 if (isError) return <div>読み込みエラーです</div>
 if (isLoading) return <div>お待ち下さい</div>
 return (<div>
  <h1>{data.targetArea}の天気</h1>
  <p>{data.text}</p>
 </div>);
}

エラー処理や読み込み中の処理なども簡潔なコードで再現でき、簡単に東京都のお天気情報を取得できました。

東京都の天気情報を表示

また、Reactのカスタムフックとしても利用できるので取得と表示の処理を分けることも。page.jsからは郵便番号だけを指定してみます。

カスタム用のgetWeather.jsのコードは以下。

import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(res => res.json());
export const getWeather = (id) => {
 const { data, isError, isLoading } = useSWR(`https://www.jma.go.jp/bosai/forecast/data/overview_forecast/${id}.json`, fetcher)
 return {data,isError,isLoading}
}

page.jsではパラメータ(郵便番号)だけを渡します。

'use client';
import {getWeather} from '@/hooks/getWeather'; 
export default function page() {
 const { data, isError, isLoading } = getWeather(140000); //郵便番号を指定
 if (isError) return <div>読み込みエラーです</div>
 if (isLoading) return <div>お待ち下さい</div>
 return (<div>
  <h1>{data.targetArea}の天気</h1>
  <p>{data.text}</p>
 </div>);
}

神奈川県の天気情報を取得できました。

神奈川県の天気情報を表示

ほかにも便利なオプションやhooksが用意されているようなので勉強してみます。公式ドキュメントは以下の通り。

Reactでinput要素に入力した内容を反映

2024/02/01 (木) - 00:00 JavaScript

Reactの勉強。Reactを使いinput要素のテキストボックスに記入した文字をパラグラフに表示できるようにしてみました。

import React, { useState } from 'react';
function Input{
 const [text, setText] = useState('');
 const onChange = (e) =>{
  setText(e.target.value);
 }
 return(
 <div>
 <input type="text" id="text" name="text" onChange={onChange} />
 <p>あなたの名前は、<strong>{text}</strong>です</p>
 </div>
 )
}
export default Input;

inputのonChangeイベントでStateを更新することで入力した内容が即時反映できます。

入力内容を反映させる

応用:入力した内容を配列にしてリストタグに追加する

先程のコードにボタン要素を追加し、ボタンを押下したら入力内容が1行ずつリストで追加されるようにしました。

import React, { useState } from 'react';
const Input = () => {
 const [text, setText] = useState('');
 const [list,setList] = useState(['りんご','ごりら']); //配列で指定
 const onChange = (e) =>{
  setText(e.target.value);
 }
 const onClick = () =>{
  setList([...list,text]); //配列に追加する
  setText(''); //入力フォームを空欄に
 }
 return(
 <div>
  <input type="text" id="text" name="text" onChange={onChange} />
  <button onClick={onClick}>追加</button>
  <ul>
  { list.map( text => {
   return (
    <li>{text}</li>
   );
  })}
  </ul>
 </div>
 )
}
export default Input;

onClickのメソッドでリスト配列を更新し、ついでにテキストボックスを空っぽにします。

入力内容を反映させる

りんご、ごりら、らっぱ、ぱせり、りんご、ごりら、らっぱ、ぱs…一旦止めます。

お名前.com ウェブドメインにAWS CloudFront&メールサーバにさくらのレンタルサーバを指定する

2024/01/24 (水) - 00:00 Server

お名前.comのドメインで契約したドメインで、www有りとwww無し(example.comwww.example.com)のドメインからAWSのCloudFrontへアクセスできるようにして、なおかつメールはさくらのレンタルサーバを使いたいときのDNS設定のメモ。

結論から言うと、お名前.comではホスト名なし(example.com)のレコードの場合Aレコード(IPアドレス)しか使えずCNAMEでCloudFrontのドメイン(*.cloudfront.net)を指定できなかったので、Route 53経由で設定することにしました。CloudFrontのIPアドレスを逆引きしてAレコードに割り当てることもできたと思いますが、万が一変動したときのリスクを考えるとRoute 53を使ったほうが安全と判断。

Route 53でホストゾーンの作成

Route 53のコンソールで、新規ドメインを追加します。[ホストゾーンの作成]から契約しているドメインを作成。完了後に表示されるネームサーバをメモに控えます。

Route 53にドメインの追加

発行されたネームサーバ。

例:
ns-xxx.awsdns-xx.com.
ns-xxx.awsdns-xx.net.
ns-xxx.awsdns-xx.org.
ns-xxx.awsdns-xx.co.uk.

お名前.comでネームサーバを設定

お名前.comのコントロールパネルから、[ネームサーバの設定]を選び、[ネームサーバーの設定]から契約しているドメインを選択し、[その他のネームサーバー]から先程、Route 53で取得したネームサーバを設定します。

  • ns-xxx.awsdns-xx.com.
  • ns-xxx.awsdns-xx.net.
  • ns-xxx.awsdns-xx.org.
  • ns-xxx.awsdns-xx.co.uk.

Route 53でレコードを設定

Route 53のコンソールにアクセスし、ホストゾーンから先ほど作成したドメインをクリック。[レコードを作成]から以下の通りレコードを作成。レコード名にはサブドメイン、空のときはそのまま空欄にします。

なお、ウェブサーバで同じAWS内のCloudFrontを指定するときは、[レコードタイプ]をAレコードにし、[エイリアス]のチェックをオンに。[トラフィックのルーティング先]からCloudFrontを選択し、[CloudFront ディストリビューション]から作成済みのCloudFrontを指定します。

Route 53にレコードを追加

その他の設定は以下の通り。

レコード名:なし

レコードタイプ:A

  • xxxxxxxxxxx.cloudfront.net.(CloudFrontのディストリビューションドメイン名)

レコードタイプ:NS(最初に発行されている)

  • ns-xxx.awsdns-xx.com.
  • ns-xxx.awsdns-xx.net.
  • ns-xxx.awsdns-xx.org.
  • ns-xxx.awsdns-xx.co.uk.

レコードタイプ:MX

  • xxxxxx.sakura.ne.jp(さくらインターネットの初期ドメイン)

レコードタイプ:TXT

  • "v=spf1 a:xxxxxx.sakura.ne.jp ~all"(さくらのレンタルサーバの指定のもの)

レコード名:www

レコードタイプ:A

  • xxxxxxxxxxx.cloudfront.net.(CloudFrontのディストリビューションドメイン名)

レコード名:mail

レコードタイプ:CNAME

  • xxxxxx.sakura.ne.jp(さくらインターネットの初期ドメイン)

設定後しばらく待ち、WebブラウザでCloudFrontが表示されることと、さくらインターネットのメールで送受信できることを確認します。DNSレコード確認ツールを使用すると、DNSレコードが正常に設定されているかチェックすることができます。

さくらインターネット(レンタルサーバー)におけるDKIMおよびDMARC対応

Gmailのメール送信者のガイドライン変更に伴い、2024年2月以降さくらのメールからGmailに正常にメールが届かなくなる可能性があります。2024年1月中旬現在、まださくらインターネットのレンタルサーバーのメールサーバではDKIM・DMARCに対応していません。今回のGmailの仕様変更に伴いさくらインターネットのレンタルサーバーのメールでもDKIM・DMARCに対応することになりました。

2024年1月中には完了するそうです。

2024年1月31日、さくらインターネットのレンタルサーバーでもDKIM・DMARCに対応しました。

関連記事

CSSで背景がぼけるすりガラスを再現

2024/01/19 (金) - 00:00 CSS

CSSでちょっとしたアクセントに使えるテクニック。macOSのコンテキストメニューやiOSの通知センターで半透明の領域で背景や壁紙がボケて見えるような演出や、古くはWindows Vistaや7のAero Glassのような見た目の実装をCSSで簡単に出来ます。半透明でボケたすりガラスのような実装ですね。

mac os コンテキストメニュー

.bgを背景、.windowを上に乗せる要素とします。背景色に透明度を指定して、さらにbackdrop-filterのプロパティを与えます。blurの値はボケの強さを指定します。

.bg{
 background: 背景写真などを指定
}
.bg p{
 background: rgba(255,255,255,0.5); /* 背景色 */
 -webkit-backdrop-filter: blur(10px);
 backdrop-filter: blur(10px); /* ぼけ具合を指定 */
}

一部ブラウザでは効かない可能性を配慮し念のため、ベンダープレフィックス(-webkit-)も付けておきました。

すりガラス

サンプルの通り、要素の位置がずれても背景がボケることがわかります。Webサイトの一部やウェブアプリケーションの演出のひとつとして使うと面白そうですね。

余談。WindowsのAero Glassは個人的にエモいUIの演出だったのに8以降は廃止されて残念です。

Next.js(App Router)で、Fetch APIを使った静的ファイル生成(SSG)でヘマした

2024/01/17 (水) - 00:00 JavaScript

静的HTMLのエクスポート(Static Exports)

WordPressのREST APIとNext.js(App Router)を使ってヘッドレスCMSを作成していた時のこと。WordPressの記事詳細や一覧をNext.jsで静的HTMLファイル(SSG)として生成してAWSS3にアップ…みたいなことをしたい。そこでnext.config.jsに以下を記載。

const nextConfig = {
output: 'export',
}
module.exports = nextConfig

開発環境で正常に稼働するかを確認する。

$ npm run dev

開発環境で、WordPressのデータ取得、記事一覧から記事詳細へのページ遷移も問題なく稼働していることを確認できました。

さて、実際にWordPressのブログを記事を静的ファイルで出力してみます。

$ npm run build

しかし、実際に出力されたファイルには404.html以外のhtmlファイルが一切なく、動的ルーティングで出力された記事一覧や記事詳細のhtmlファイルも一切吐き出しされませんでした…。

┌ λ /
├ ○ /_not-found
├ ● /[slug]
├   ├ /hogehoge
├   ├ /fugefuge
…
○  (Static)   prerendered as static content
●  (SSG)      prerendered as static HTML (uses getStaticProps)
λ  (Dynamic)  server-rendered on demand using Node.js

よく見るとドキュメントルートのディレクトリがλ (Dynamic) と動的レンダリング(SSR)になっています。なぜだろう?と思ってコードを調べるとfetchの仕方に問題が有りました。

const url = await fetch(process.env.JSON_URL,{cache: 'no-store'}).then(〜);

fetchでキャッシュをcache:no-storeにすると、動的レンダリング(SSR)になってしまいます。

これはドキュメントにも書いてあります。そこで、fetchを以下のようにしたところ

const url = await fetch(process.env.JSON_URL).then(〜);

正常に静的レンダリングが行われ、静的HTMLファイルが出力されました。ドキュメントはよく読もう、わたし。

┌ ○ / 
├ ○ /_not-found
├ ● /[slug]
├   ├ /hogehoge
├   ├ /fugefuge
…
○  (Static)   prerendered as static content
●  (SSG)      prerendered as static HTML (uses getStaticProps)

次回、WordPressを出力するのコードを掲載予定(覚えてれば)

Youtube iFrame Player APIでスクロールして見えたら動画を自動再生する

2024/01/12 (金) - 00:00 JavaScript

Youtubeのiframe埋込みタイプを使い、ページをスクロールして動画が見えたら自動で動画が再生させたい場合。Youtube iFrame Player APIを使えば出来ます。まず、JavaScriptでiframe_apiを予め読み込ませておきます。

<script src="https://www.youtube.com/iframe_api" async></script>

動画はYoutubeの[共有]→[埋め込み]で出力されるタグをそのままHTMLの任意の位置に設置します。

Youtube動画埋め込み

ただし自動再生させるため、制御用ID属性とパラメータを追加します。APIでプレーヤー制御を許可させるパラメータenablejsapi=1、iOS Safariなどでインライン再生させるためのパラメータplaysinline=1を指定します。後は任意で好きなパラメータを追加します。

<iframe id="【iframeのID名】" src="https://www.youtube.com/embed/【動画ID】?enablejsapi=1&playsinline=1&controls=0&loop=99&modestbranding=1&playlist=【動画ID】" width="560" height="315" frameborder="0" allowfullscreen="allowfullscreen"></iframe>

JavaScriptのコードは以下の通りとなります。スクロールして埋め込みiframe要素が画面に入ったら動画を再生し、画面外になったら一時停止するものとなります。自動再生させるときは必ずYoutube APIのmute()メソッドで音声をミュートにさせる必要があります。これを入れないと動画は自動再生されません。

const youtubeElement = document.getElementById('【iframeのID名】');
function onYouTubeIframeAPIReady() {
 const youtube = new YT.Player('【iframeのID名】', {
  events: {
  'onReady': onReady,
  }
 });
}
function onReady(e){
 scrollEvent(e.target);
}
function scrollEvent(t){
 let youtubeTop = 0;
 window.addEventListener('scroll', function() {
  youtubeTop = window.pageYOffset + youtubeElement.getBoundingClientRect().top;
  if((youtubeTop < window.pageYOffset + window.innerHeight) && (youtubeTop > window.pageYOffset)) {
   t.mute(); //必ず音声をミュートにする
   t.playVideo();
  }else{
   t.pauseVideo();
  }
 }, false);
}

また動画はユーザーのメモリや通信料削減のため、長時間動画は流さないように注意しましょう。

自治体Webサイトの災害時・緊急時対策

2024/01/06 (土) - 03:00 Others

2024年1月1日に石川県・能登地方を震源とする最大震度7の地震と津波が発生いたしました。福井県、新潟県等広い範囲で揺れが観測され被害も発生しております。まずは被災された方々にお見舞いを申し上げます。

災害大国とされている日本では、平成・令和になってからも度々大きな地震や津波、台風や豪雨など大規模な自然災害が度々発生しています。災害時、情報を把握しようと都道府県や自治体のWebサイトに県内外問わず多くのアクセスが殺到し、アクセス障害になることがあります。

地域の避難情報や現在の状況の把握する役割としてWebサイトは重要ですが、災害時にアクセスが殺到してWebサイトが停止して情報が見られなくなるような事態は最も避けなくてはなりません。今回は災害時、緊急時の対策例を紹介します。

災害用軽量版Webサイトへの切り替え

通常の金沢市のWebサイト。

金沢市のWebサイト(通常)

Webサイトにアクセスすると、HTMLのほか関連するリソース(画像やCSS、JavaScriptなど)が読み込まれ、読み込むファイルが多ければ多いほどサーバのリクエストが増えサーバーに負担がかかります。負担がかかるとサーバーの処理が重くなり、待ち時間も増えてなかなか表示されないという事態にもなります。そしてやがて限界を突破するとサーバーがダウンしてしまいます。

また、パソコンや携帯電話やスマートフォンなどアクセスする端末でも読み込みが遅くなりWebサイトが表示されるまでに時間がかかります。とくに被災地などでは通信回線がパンクしたり、回線が破損する事で通信が快適に行えない事態も発生する可能性があります。とくに災害時は自由に電源確保ができない可能性が高いため、端末のバッテリーも消費して結果としてバッテリーが無くなってしまうことも懸念されます。

災害時の金沢市のWebサイト。

金沢市のWebサイト(災害時用)

実際に、平成16年に発生した新潟県中越地震の際に自治体へのWebサイトへのアクセスが集中し、アクセス障害が発生したことが問題となり、緊急としてCSSや画像による視覚情報を排除したテキストだけの表示に切り替えたところ、サーバーの負荷の軽減に成功したことが評価されました。以降もこの事例を教訓に総務省をはじめ自治体や行政でも対策が行われるようになりました。また行政関連のWebサイトのWeb標準に準拠したHTMLとCSSレイアウトの推進にも繋がりました。

以降、災害発生時や非常事態時はWebサーバの負担とデータ通信量を軽減するため、画像やデザインなどを極力排除した軽量版のWebサイトに切り替えるのが一般的となっています。平成23年に発生した東日本大震災の際は、東北地方の自治体のWebサイトをテキスト情報のみのシンプルなWebサイトに切り替えられるようなシステムを構築しておき、Webサイトのダウンを防ぐこともできました。

Webサイトのデータ量を軽量化することで、アクセスも短時間で行うことが出来るため、結果として端末のバッテリー消費も最小限で済ますこともできます。また、公共放送であるNHK(日本放送協会)でも災害時用に軽量化したページに切り替える対策が取られています。

NHKのWebサイト(災害時用)

ほかにも以下のような対策をすることで、WebサーバやWebサイトの軽量化、読み込みの高速化、端末の負荷軽減を図ることができます。

  • トップページには必要な情報、緊急情報だけを掲載する
  • 画像や重い演出を極力排除する
  • CDNやキャッシュを使い読み込みの向上を図る
  • 柔軟に増強できるクラウドサーバを利用する
  • リソース(画像や映像を配置する)サーバとフロントサーバを分ける
  • ミラーサイトの設置、ロードバランサーによるアクセス分散化
  • ネットワーク障害発生の予備の用意・冗長化
  • Webサイトの背景をを黒くして液晶画面のバッテリー消費量を下げる
  • JavaScriptなどで端末のバックグランドで行われる処理を停止し端末のリソース消費量を下げる

普段の備えと同じでWebサイトでも対策をすることが大切です。

ページの先頭へ