たくさんの自由帳
Androidのお話
たくさんの自由帳
文字数(だいたい) : 7295
どうもこんばんわ。
特に書くことがないのでお正月にWSL 2で遊んでた時の画像でもおいておきます。いつかWSL 2の記事も書きたい!

Next.js 15
Next.js 15 introduces React 19 support, caching improvements, a stable release for Turbopack in development, new APIs, and more.
https://nextjs.org/blog/next-15

Next.js 15.1
Next.js 15.1 introduces React 19 stable support, improved error debugging, new experimental authorization APIs, and more.
https://nextjs.org/blog/next-15-1
Next.js 15をやろうとして気付いたらあけおめ。React 19もRCが外れたのでいい加減やります。ついでに修正とかもしたいけど今は移行だけします。
見た感じそんな複雑じゃ無さそうなので、gitでいつでも戻せることを確認した上で、codemodを叩いてみる。
npx @next/codemod@canary upgrade latestYes、yで。
Need to install the following packages:
@next/codemod@15.1.1-canary.25
Ok to proceed? (y) yお、Turbopackが選べる?
でもこのNext.js 製ブログはwebpackに依存してたような、、、
? Enable Turbopack for next dev? » (Y/n)複数の修正をやってくれるそう。矢印上下キーで移動、スペースでチェックのON/OFF、A キーですべて選択、Enter キーでcodemod実行。
React 19側のcodemodもやるか?。やるか。
? Would you like to run the React 19 upgrade codemod? » (Y/n)
? Would you like to run the React 19 Types upgrade codemod? » (Y/n)進めてるとデプロイ先にVercelを選んでいるか聞かれた。
静的サイトかつAWSなのでnで。
? Is your app deployed to Vercel? (Required to apply the selected codemod) » (Y/n)なんか入れないといけないらしい。いれるか。yでエンター。
Need to install the following packages:
types-react-codemod@3.5.2
Ok to proceed? (y)終わった。
✔ Codemods have been applied successfully.
Please review the local changes and read the Next.js 15 migration guide to complete the migration.
https://nextjs.org/docs/canary/app/building-your-application/upgrading/version-15page.tsxとroute.tsxが
Next.js 15 の codemod を実行しました。 · takusan23/ziyuutyou-next@008f234
https://github.com/takusan23/ziyuutyou-next/commit/008f234a9ecc215f5d2afc43b469cbac40bc2f04
codemodを叩いた際にTurbopack有効を選んだので、開発時はTurbopackが有効になるオプションが指定されています。
"scripts": {
"dev": "next dev --turbopack",あと一番下になにか追加されてる。
"overrides": {
"@types/react": "19.0.2"
}ページのURL パス(動的ルーティングのあれ)とかを関数の引数にPropsとしてつけていましたが、このPropsがPromise<Props>のように、非同期でURLのパラメータとかが渡ってくるようになりました。
手動で直す場合は、まず各ページの引数の型をPromise<>でかこって、
async functionでpage.tsxを書いている場合はawait propsすれば良さそう(codemodがそうしてた)How to upgrade to version 15
Upgrade your Next.js Application from Version 14 to 15.
functionでpage.tsxしている場合は、PromiseをReactから読み取るReact 19の新機能、use()のHookを使えば良いって書いてあります。How to upgrade to version 15
Upgrade your Next.js Application from Version 14 to 15.
以下async functionなpage.tsxを直した際の差分。
diff --git a/app/posts/[blog]/page.tsx b/app/posts/[blog]/page.tsx
index 228d44e..d6cc1a7 100644
--- a/app/posts/[blog]/page.tsx
+++ b/app/posts/[blog]/page.tsx
@@ -14,11 +14,12 @@ import "../../../styles/css/content.css"
/** 動的ルーティング */
type PageProps = {
- params: { blog: string }
+ params: Promise<{ blog: string }>
}
/** head に値を入れる */
-export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
+export async function generateMetadata(props: PageProps): Promise<Metadata> {
+ const params = await props.params;
const markdownData = await ContentFolderManager.getBlogItem(params.blog)
const ogpTitle = `${markdownData.title} - ${EnvironmentTool.SITE_NAME}`
const ogpUrl = `${EnvironmentTool.BASE_URL}${markdownData.link}`
@@ -41,7 +42,8 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
* 記事本文。
* 反映されない場合はスーパーリロードしてみてください。
*/
-export default async function BlogDetailPage({ params }: PageProps) {
+export default async function BlogDetailPage(props: PageProps) {
+ const params = await props.params;
// サーバー側でロードする
const markdownData = await ContentFolderManager.getBlogItem(params.blog)Route Handlers (API Routes)のroute.ts (.tsx)に関しても同様にPromiseでPropsを渡してくれる設計だそうです。
このサイトではOGP画像の生成で使ってて、静的書き出し時にGETリクエストされOGP画像も静的書き出しされます。
diff --git a/app/posts/[blog]/opengraph-image.png/route.tsx b/app/posts/[blog]/opengraph-image.png/route.tsx
index af60d1a..488e623 100644
--- a/app/posts/[blog]/opengraph-image.png/route.tsx
+++ b/app/posts/[blog]/opengraph-image.png/route.tsx
@@ -7,7 +7,7 @@ import FileReadTool from "../../../../src/FileReadTool"
/** 動的ルーティング */
type PageProps = {
- params: { blog: string }
+ params: Promise<{ blog: string }>
}
/**
@@ -17,7 +17,8 @@ type PageProps = {
* 使える CSS は以下参照:
* https://github.com/vercel/satori
*/
-export async function GET(_: Request, { params }: PageProps) {
+export async function GET(_: Request, props: PageProps) {
+ const params = await props.params;
// 記事を取得
const markdownData = await ContentFolderManager.getBlogItem(params.blog)私の環境では以上で、codemodがすべて直してくれました。
webpackに依存してたような、、、それでTurbopackにしたから動かないのかも。
React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.
React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.
⨯ [Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.] {
digest: '356132257'
}SVGRはSVGのファイルをReactのコンポーネントとして表示できるやつで、import Icon from 'icon.svg'みたいにsvgをimportするだけで、Reactのコンポーネントとして<Icon />みたいに使えるあれ。便利。
Turbopackはwebpack使えないものだと思ってたんですが、Turbopackでも@svgr/webpackは動くって言ってる。ん?turbopack
Configure Next.js with Turbopack-specific options
どうやらこの通りに書くと動くらしい。やってみる
turbopack
Configure Next.js with Turbopack-specific options
[turbopack] SVG via svgr support · Issue #4832 · vercel/turborepo
What version of Turborepo are you using? next@13.4.1 What package manager are you using / does the bug impact? npm What operating system are you using? Mac Describe the Bug In Next.js, I use the SV...
https://github.com/vercel/turborepo/issues/4832
next.config.js
/** @type {import('next').NextConfig} */
module.exports = {
// この webpack と
webpack(config) {
// SVG をコンポーネントにできる
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack'],
})
return config
},
// これを追加
experimental: {
turbo: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
}
}
}Turbopack、速い。。と思う。私の作りが悪い可能性も十分あるけど。
このサイトはNext.jsの output: 'export'で動いています。
書き出した成果物をS3にあげCloudFrontでお届けしています。
npm run buildしたら次のエラーが
✓ Linting and checking validity of types
Collecting page data ..Error: export const dynamic = "force-static"/export const revalidate not configured on route "/sitemap.xml" with "output: export". See more info here: https://nextjs.org/docs/advanced-features/static-html-export
at <unknown> (C:\Users\takusan23\Desktop\Dev\NextJS\ziyuutyou-next\.next\server\app\sitemap.xml\route.js:1:1127)sitemap.tsを静的書き出ししようとしたときのエラー。export const dynamicかexport const revalidateが無いから怒られてる?
export const dynamic = "force-static"とかは、Next.js自体をサーバーで動かす場合(Vercel、レンタルサーバー、その他)に使うやつで、
あらかじめ生成する静的書き出しモードだと別に指定しなくて良いんじゃないの?って思ってたけど明示的にforce-staticって書かないとだめなのかな。
静的書き出しだとビルドコマンドを叩いた時点ですべてのHTMLが生成されるので、書かずとも全部force-staticになるけど。
Issue見に行ったらあった。以下のようにforce-staticの一文をとりあえず足したら直った。SSGなのに足さないと行けないのか・・?`robots.txt` generation from `robots.ts` fails with `output: "export"` config in Next 15 RC & Canary.108 · Issue #68667 · vercel/next.js
Link to the code that reproduces this issue https://codesandbox.io/p/devbox/robots-txt-bug-xx45yn To Reproduce Build a static export with next build Current vs. Expected behavior After building the...
https://github.com/vercel/next.js/issues/68667
// 静的書き出しなので指定する必要がないはずだが、Next.js 15 から無いとエラーになってしまう
// https://github.com/vercel/next.js/issues/68667
export const dynamic = "force-static"
/**
* サイトマップを生成する。Next.js 単体で作れるようになった。
* Trailing Slash が有効なので最後にスラッシュ入れました。
*/
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
// 以下省略...
}次はこれ。
Image data URI resolved without size:data:image/svg+xml;,<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"> ... </svg>
Image data URI resolved without size:data:image/svg+xml;,<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"> ... </svg>
Error occurred prerendering page "/posts/amairo_kotlin_coroutines_flow/opengraph-image.png". Read more: https://nextjs.org/docs/messages/prerender-error
Error: SVG data parsing failed cause invalid attribute at 1:44192 cause expected '"' not '<' at 1:44219
at imports.wbg.__wbg_new_15d3966e9981a196 (file:///C:/Users/takusan23/Desktop/Dev/NextJS/ziyuutyou-next/node_modules/next/dist/compiled/@vercel/og/index.node.js:18406:17)ちなみにこれは開発時でも同じエラーがでました。開発時よりも↑の本番のほうがまだわかりやすいエラーなの、開発時はTurbopackとかが影響してんのかな。
まあ今まで動いてたので分かんないんだけど。
⨯ [TypeError: The "payload" argument must be of type object. Received null] {
code: 'ERR_INVALID_ARG_TYPE',
page: '/posts/nextjs_15_migration/opengraph-image.png'
}そもそも静的書き出しモードでOGP画像の生成はドキュメント通りに作るとダメで(Issueある)、代わりにOGP画像を返すRoute Handlersを作ることで静的書き出し時にHTMLとともに画像が生成されるようになります。
そのRoute Handlers機能も本当は静的書き出しモードでは利用できないのですが、条件を満たした(GETリクエストのみ + Requestの引数を使わない)場合は静的書き出し時に一緒に呼び出され書き出されるようです。
本題に戻して、OGP画像にSVGを<img>経由で表示しているのですが、これがなんかエラーになってしまっている。
かなり端折っていますが、こんな感じに<img>でSVGを表示させてた。<img>を経由してたのはpublic/にあるアイコンを読み出したく、ファイルパスより直接渡したほうが良いらしいので。
// 本当は opengraph-image.tsx を作って使います
// ImageResponse を返すことで OGP 画像を返せる Route Handlers が作れる。静的書き出し時はこっちで作る必要がある
export async function GET(_: Request, props: PageProps) {
const params = await props.params;
return new ImageResponse(
(
// 背景
<div
style={{
height: '100%',
width: '100%',
position: 'relative',
display: 'flex',
backgroundColor: '#fff'
}}
>
{/* SVG は https://fonts.google.com/icons から。ありざいす */}
<img
width={50}
height={50}
src={
`data:image/svg+xml;,<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z"/></svg>`
}
/>
</div>
),
{
width: 1200,
height: 630
}
)
}マジでエラーが分からんのでNext.jsがOGP画像生成で使ってるライブラリvercel/satoriのエラーを出してる箇所を見に行ったところ、data:image/svg+xml;,だと間違いで、これでは正規表現が一致しなくて、追加でエンコーディングが何なのかをいれる必要がありました。
なので正解は→ data:image/svg+xml;charset=utf8,{SVGのxmlをここに貼る}
てかGoogleで調べたらutf-8まで入れてたわ。
ちなみにこのdata:から始まるやつ、Data URI (URL ?)とかいう名前がついているらしい。
export async function GET(_: Request, props: PageProps) {
const params = await props.params;
return new ImageResponse(
(
// 背景
<div
style={{
height: '100%',
width: '100%',
position: 'relative',
display: 'flex',
backgroundColor: '#fff'
}}
>
{/* SVG は https://fonts.google.com/icons から。ありざいす */}
<img
width={50}
height={50}
src={
`data:image/svg+xml;charset=utf8,<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z"/></svg>`
}
/>
</div>
),
{
width: 1200,
height: 630
}
)
}playgroundで試したい方→ 
Vercel OG Image Playground
Generate Open Graph images with Vercel’s Edge Function.
https://og-playground.vercel.app/
Failedって出てビビったけど、しばらくした後にリトライしてくれるらしい。
Failed to build /posts/page/[page]/page: /posts/page/1 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/2 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/3 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/4 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/5 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.ただ、後述しますがGitHub Actionsの環境だとこれがよく出てしまってそうなので、タイムアウトを伸ばすようにしました。。
ドキュメントあった。
進捗ない場合はタイムアウトさせて再起動する機能がついたそう。
どうやらNext.js 15からAdvanced Static Generation Controlという、静的書き出しの並列数等を制御する設定が実験的に追加されたそう。
本当にひどいようならここをいじるしか無い?
GitHub Actionsが走っています
ローカルと同じエラーが出てしまった。タイムアウトを伸ばさないから、、、
とりあえず今までのCIが5分以内で終わってたので5分、、、と思ったんですけどさっきのCIが4分かかってるので2倍にした。
/** @type {import('next').NextConfig} */
module.exports = {
output: 'export',
// 省略...
staticPageGenerationTimeout: 60 * 10 // 10分
}で、伸ばした後リトライしたら逆にいつもの時間で終わった、どういうことなの
でも何回かGitHub Actionsやってるけどやっぱりこのエラーが出る時は出てしまうので伸ばしておくことにしようと思う。
Failed to build /posts/page/[page]/page: /posts/page/1 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/2 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/3 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/4 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/5 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/6 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/7 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/8 (attempt 1 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/4 (attempt 2 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/6 (attempt 2 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/7 (attempt 2 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/8 (attempt 2 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/3 (attempt 2 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/1 (attempt 2 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/5 (attempt 2 of 3) because it took more than 60 seconds. Retrying again shortly.
Failed to build /posts/page/[page]/page: /posts/page/2 (attempt 2 of 3) because it took more than 60 seconds. Retrying again shortly.なんでこんな部分的に壊れるの...?
ちなみにエラー。分からん、、
Generating static pages
Failed to load dynamic font for 自由帳経動直 . Error: [TypeError: fetch failed] {
[cause]: [AggregateError: ] { code: 'ETIMEDOUT' }
}
Failed to load dynamic font for 自由帳国内版入 . Error: [TypeError: fetch failed] {
[cause]: [AggregateError: ] { code: 'ETIMEDOUT' }
}
Failed to load dynamic font for 自由帳作直際大変 . Error: [TypeError: fetch failed] {
[cause]: [AggregateError: ] { code: 'ETIMEDOUT' }
}
Generating static pagesなんかまたタイムアウト系なのでもう一回試したら動いた。なんかすごい →うそです。静的書き出しモードの調子悪い?OGP画像の件に関しては私が悪かったです。
Generating static pages
Generating static pages
✓ Generating static pages
Finalizing page optimization ...
Collecting build traces ...
Exporting (0/3) ...
✓ Exporting (3/3)これ次回以降もなにかの拍子に失敗する可能性があるってこと?どうしたものか
これに関しては私が悪かったです、原因はフォントファイルがWeb 向けの軽量版を使ってて、必要な文字が網羅されていないのが原因だった、
今まで気付かなかった。。。全部入りの方を使うようにしました。
Next.jsのOGP画像を作る機能はvercel/ogを使っていて、そのライブラリはvercel/satoriを使っています。
vercel/satoriを直接使っていないのか、vercel/ogは何なのかはこちらにまとまっていました。Google Fontsからダウンロードする機能、satoriが吐き出したSVGをpngにする等をvercel/og側でやっているらしい。
OGP 画像を作る時に @vercel/og を使うか satori を使うか迷ったログ | t28.dev
https://t28.dev/blog/vercel-og-or-satori-for-me
話を戻して、じゃあWindowsマシンで成功してGitHub Actionsで失敗してんのはなんでだよって話ですが、おそらくこれです。
そのフォントを指定しない場合(もしくはフォントファイルに文字がない場合)はGoogle Fontsからダウンロードするのですが、Windowsはいい感じにIPv4で繋いでくれたけどGitHub Actions(というか Linux)だとおま環で繋がらないIPv6の方を使ってしまってるのが原因の可能性がある。
いきなり話が広がりすぎやろがいって話ですが、WSL 2(Ubuntu)でビルドする際にIPv4を優先するオプションを付けるとだいぶエラーの数が減ったので可能性がある。減っただけでエラー自体はある。
こんな感じにnodeのオプションをつければ良いはず?
NODE_OPTIONS='--dns-result-order=ipv4first' npm run buildもし私みたいな事になってしまったら、Web 向けの軽量版フォントを使うのを辞めるか、Google Fontsをあらかじめローカルに落としておいて、satoriにフォントファイルとしてArrayBufferで渡すなどすれば良いのかな。
ちな上記のオプションIPv4を強制するものじゃないのでIPv6を使われたらやっぱりエラーになってしまう。
これ一時的にIPv6がダメだったり、Node.jsのバージョンが関与してたり(?)でかなり複雑そう雰囲気。。。
ちなみにReact側の修正は ないはずです。 私の場合はありませんでした
React 19のuse() Hookと<Suspense>でuseEffect()を消し飛ばしたいnext.config.ts。TypeScriptで書けるようになったやりたいんですが、あんまり決まってないのでとりあえずNext.js 15に上げただけのPRを書こうと思います。Tailwind CSSも次のバージョンが控えてるそうなので今じゃなくていいか。
フロントエンド、むずかしい。
せいてきがえっちな方の漢字になってないかgrepしたけど大丈夫そう