以前、こんな記事を書いた。
npx next は罠!非推奨! #TypeScript - Qiita
npx next コマンドで立てることができるサーバーではページの表示が遅く、npx next build では発見できるエラーを発見できないことがあるため、使わないほうがいいと主張する記事である。
しかし、先日、逆に npx next build では見逃し、npx next では指摘してくれるエラーを発見した。
今回はこれを紹介する。
間違っているコード
mikecat/error-spotted-by-npx-next: npx next build で発見できなかったエラーを npx next が発見する回
'use server';
export async function greetAction(name: string): Promise<string> {
return `Hello, ${name}!`;
}
'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
import { greetAction } from './action';
function getGreeting(name: string): Promise<string> {
return greetAction(name);
}
function GreetingDisplay({ greetingPromise } : { greetingPromise: Promise<string> }) {
const [greeting, setGreeting] = useState('');
useEffect(() => {
greetingPromise.then((result) => {
setGreeting(result);
}, (error) => {
console.error(error);
});
}, [greetingPromise]);
return (
<p>{greeting}</p>
);
}
export default function Home() {
const [name, setName] = useState<string | null>(null);
const nameInputRef = useRef<HTMLInputElement>(null);
const setNameHandler = useCallback(() => {
if (nameInputRef.current) setName(nameInputRef.current.value);
}, []);
return (
<>
<form action={setNameHandler}>
<p>
<input type='text' ref={nameInputRef} defaultValue='' size={20} />
<button type='submit'>send</button>
</p>
</form>
{name !== null ? (
<GreetingDisplay greetingPromise={getGreeting(name)} />
) : null}
</>
);
}
これは、入力欄に文字列を入れてボタンを押すと、それを Server Actions を用いて加工し、結果を表示するプログラムである。
npx next build でビルドを行い、npx next start でサーバーを起動し、Firefox でアクセスして操作を行った結果、エラーは出ずに意図した表示となった。
ビルド時、および実行時のサーバーでも、エラーは出ていない。
npx next で実行した際のエラー
このコードを npx next で立てたサーバーで実行すると、以下のエラーが出た。
Cannot update a component (`Router`) while rendering a different component (`Home`). To locate the bad setState() call inside `Home`, follow the stack trace as described in https://react.dev/link/setstate-in-render
パッと見、今回書いたコードでは Router なんてコンポーネントは定義しておらず、意味不明なメッセージに思える。
とはいえ、よく考えると、レンダー時に getGreeting 関数を呼び出しており、これが Server Actions である関数 greetAction を呼び出しているので、「レンダー時に副作用を起こしてはいけない」という原則に違反していることがわかる。
エラーの解消
今回のエラーは、getGreeting 関数の呼び出しを useEffect で実行される関数の中に移動することで解消できる。
mikecat/error-spotted-by-npx-next at fix
'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
import { greetAction } from './action';
function getGreeting(name: string): Promise<string> {
return greetAction(name);
}
function GreetingDisplay({ name } : { name: string }) {
const [greeting, setGreeting] = useState('');
useEffect(() => {
const greetingPromise = greetAction(name);
greetingPromise.then((result) => {
setGreeting(result);
}, (error) => {
console.error(error);
});
}, [name]);
return (
<p>{greeting}</p>
);
}
export default function Home() {
const [name, setName] = useState<string | null>(null);
const nameInputRef = useRef<HTMLInputElement>(null);
const setNameHandler = useCallback(() => {
if (nameInputRef.current) setName(nameInputRef.current.value);
}, []);
return (
<>
<form action={setNameHandler}>
<p>
<input type='text' ref={nameInputRef} defaultValue='' size={20} />
<button type='submit'>send</button>
</p>
</form>
{name !== null ? (
<GreetingDisplay name={name} />
) : null}
</>
);
}
結論
npx next にはページの表示が遅い、検出されないエラーがあるなどの問題があるものの、逆に npx next では検出されるが npx next build では検出されないエラーがあることがわかった。
npx next build だけに頼るのではなく、時々は npx next で実行してのチェックも行うべきなようだ。


