Next 14で静的ビルド(next export)した時にハマったこと備忘録
概要
久々にnextでの開発を仕様として静的サイト作成時に詰まったので解決した記録を残す
next.configはこれ
/** @type {import('next').NextConfig} */
const nextConfig = {
distDir: 'build',
output: 'export',
sassOptions: {
},
pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
compiler: {
styledComponents: true,
},
};
export default nextConfig;
問題1: Page "xxxx" is missing "generateStaticParams()"
Next14で静的ビルドする時、動的なページパス(page/[id])とかは明示的にどんなパスが生成されるか、generateStaticParams
という関数をページのファイルにこちらで定義しないといけないらしい
公式doc https://nextjs.org/docs/app/api-reference/functions/generate-static-params
詳しい例
例えば記事のIDごとに articles/[id]
というURLで動的なページを作りたいとき
ファイル内にパラメータとして返す値を定義した配列を返す関数を generateStaticParams
という名前で定義しないといけない
import STArticle from '@/cocmponents/templates/STArticle/STArticle'
import { ARTICLE_LIST } from '@/const/articles/article_list'
// 記事にアクセスする際のパラメータとして使用される値を配列として返す関数をgenerateStaticParamsという命名で定義する必要がある
export const generateStaticParams = () => {
return ARTICLE_LIST.map((article) => ({
id: article.id,
}));
/**
↑は記事の配列から返してるけど下記の配列が返ってくればいい
[{id: 'hoge'}, {id: 'fuga'}]
id の部分はディレクトリ名として定義した命名([id])と一致させる
*/
};
const ArticlePage = ({params}) => {
const articleId = params.id
const ARTICLE = ARTICLE_LIST.find((article) => article.id === articleId)
return <>
<STArticle article={ARTICLE}/>
</>
}
export default ArticlePage
問題2: Error: Could not find a production build in the 'build' directory. Try building your app with 'next build' before starting the production server.
概要
next build
というコマンドでbuildというディレクトリに静的ファイルが生成されても next start
時に 「buildファイルが見つからない」という意味のエラーになる
結論
静的サイト生成時(nuext.configでmode: exportにしているとき)は nuxt start を使わない
ローカルでサーバを実行して生成したディレクトリを配信すればいい
npx serve@latest 生成したディレクトリ
解決ログ
エラーが起こっているライブラリ内のコードでは BUILD_ID
というファイルを読みに行ってエラーになっている
try {
// ここでエラーが起こってる
buildId = await _promises.default.readFile(buildIdPath, "utf8");
} catch (err) {
if (err.code !== "ENOENT") throw err;
throw new Error(`Could not find a production build in the '${opts.config.distDir}' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id`);
}
↓エラーの内容をconsole.logしてみた
[Error: ENOENT: no such file or directory, open 'C:\Users\xxx\build\BUILD_ID'] {
errno: -4058,
code: 'ENOENT',
syscall: 'open',
path: 'C:\\Users\\xxx\\build\\BUILD_ID'
}
C:\Users\xxx\build\BUILD_ID
というファイルががいないと言われてるけど
実際にnext buildで生成されたファイルにも build/BUID_ID
はいない
試しに build/BUULD_ID ファイルを作ってみるとError: ENOENT: no such file or directory, scandir 'C:\xxx\build\static'
エラーが出る
staticを _nextから移すと今度はError: ENOENT: no such file or directory, open 'C:\xxx\build\routes-manifest.json'
というエラーが出る
ここからそもそもbuild時に生成される生成物が静的サイト用の物ではない可能性がわかる
next.config
のdistDir: 'build'
が悪いのかと思い消して、next build > next start してみる
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
sassOptions: {
},
pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
compiler: {
styledComponents: true,
},
};
export default nextConfig;
下記エラーが出る
Error: "next start" does not work with "output: export" configuration. Use "npx serve@latest out" instead.
npx serve@latest out
を実行してみる
serveできた
distDir: 'build' を再度next.configに定義して試してみる > 配信できる
↓このメッセージは生成ディレクトリがデフォルトの out
以外の時は出てくれないんかい…
Error: "next start" does not work with "output: export" configuration. Use "npx serve@latest out" instead.
問題3: 生成された静的ディレクトリをローカルサーバで配信しても画像が表示されない
結論
静的画像では import Image from 'next/image'
ではなく img タグを使う
{/* eslint-disable @next/next/no-img-element*/}
<img
alt={props.alt}
src={imgData}
width="100"
height="100"
sizes="100vw"
loading="lazy"
style={{
width: '100%',
height: 'auto',
}}/>
{/* eslint-enable @next/next/no-img-element*/}
ちなみにimgタグでonLoadが効かなかったので仕方なくnew Imageでの画像読み込み完了を判定しました↓
const [isLoadingEnd, setIsLoadingEnd] = useState(false)
const [isLoadingStart, setIsLoadingStart] = useState(false)
const loadImage = useCallback(async () => {
try {
setIsLoadingStart(true)
const RenderImage = new Image()
RenderImage.src = props.src
await RenderImage.decode()
} catch (e) {
console.log(e)
}
setIsLoadingEnd(true)
}, [props.src])
useEffect(() => {
if(!isLoadingStart) {
loadImage()
}
}, [isLoadingStart, loadImage])
解決ログ
画像自体は存在していている
ブラウザでは_nuxt/imageのパスを見に行ってしまっていてエラーになっている?
他記事では Image ではなく素のimgタグを使用することで解決していた
Discussion