Вопрос или проблема
Я пытаюсь понять проблему, с которой мы сталкиваемся в нашем приложении Next.js версии 14 (route приложения).
У нас есть страница с серверным рендерингом (SSR), которая сначала проверяет JWT токен, вызывая конечную точку verify-token. Если токен действителен, мы рендерим форму (директива use-client) на этой странице. Форма использует действие на стороне клиента для проверки пользовательского ввода. После успешной проверки она инициирует действие на стороне сервера, которое взаимодействует с API для регистрации пользователя.
Проблема, которую мы наблюдаем, заключается в том, что после успешной отправки формы функция verifySignupToken вызывается снова. Поскольку мы выполняем навигацию на стороне клиента после отправки формы с помощью router.push, нам следует сразу перейти на новый маршрут? Я не уверен, почему она снова инициирует вызов fetch в родительском компоненте?
Страница SSR (с конфигурацией force-dynamic):
export const dynamic="force-dynamic"
export default async function Signup({ searchParams }: SignUpProps) {
const { token, customerId, email } = searchParams
const result = safeParse(VerifySignupSchema, { token, email, customerId })
if (!result.success) {
console.error(result.issues)
redirect('/login?error=validation')
}
const validatedData = result.output
try {
await verifySignupToken(validatedData.token, validatedData.customerId, validatedData.email)
} catch (error) {
if (error instanceof Error) {
redirect(`/login?error=${encodeURIComponent(error.message)}`)
}
}
return (
<SignUpForm
token={validatedData.token}
customerId={validatedData.customerId}
email={validatedData.email}
/>
)
}
SignUpForm (директива use-client с действием на стороне клиента, вызывающим действие на стороне сервера):
export const SignUpForm = ({ token, customerId, email }: SignUpFormProps) => {
const router = useRouter()
const { pending } = useFormStatus()
const signUpClientAction = async (formData: FormData) => {
const password = formData.get('password')
const pnr = formData.get('pnr')
const result = safeParse(SignUpSchema, { password, pnr })
if (!result.success) {
toast.error(result.issues[0].message)
return
}
const validatedData = result.output
const response = await signupAction(
email,
validatedData.password,
token,
validatedData.pnr,
customerId
)
if (response.success) {
toast.success(response.message)
router.push("https://stackoverflow.com/")
} else if (response.statusCode === 401) {
toast.error(response.message)
router.push('/login')
} else {
toast.error(response.message)
}
}
return (
<div className="mt-10">
<div>
<form className="space-y-6" action={signUpClientAction}>
....
.
Ответ или решение
Проблема, с которой вы столкнулись в вашем приложении на Next.js (версии 14 с использованием App Router), связана с тем, что после отправки формы происходит повторный вызов функции verifySignupToken
, несмотря на использование клиентской навигации. Это может вводить в заблуждение, особенно если вы ожидаете, что после успешной отправки формы пользователь будет перенаправлен на новую страницу.
Почему происходит повторный вызов verifySignupToken
?
-
Серверный рендеринг (SSR): Поскольку страница рендерится на сервере, вызов
verifySignupToken
на стороне сервера осуществляется каждый раз при запросе к этой странице. С учетом того, что вы используетеdynamic="force-dynamic"
, этот компонент будет обрабатываться как динамичный, но не гарантирует отсутствие повторного вызова при навигации. Сервер всегда будет обрабатывать запрос при каждой навигации на эту страницу, если это не будет явно указано. -
Перенаправление и повторный запрос: Когда ваш
SignUpForm
отправляет данные через клиентское действиеsignUpClientAction
и, при успешном ответе, выполняетсяrouter.push
, это скорее технически создает новый запрос к странице. Если рендеринг этой страницы требует валидации JWT, то вся логика в функции компонента будет заново вызвана, включая верификацию токена.
Как можно решить проблему?
Вот несколько рекомендаций, которые могут помочь избежать повторной валидации JWT после успешной отправки формы:
1. Перенаправление после отправки формы
Вместо того чтобы напрямую отправлять форму с помощью action={signUpClientAction}
, вы можете использовать обработчик события onSubmit
. Это позволит вам управлять процессом перенаправления более явно и избежать ненужного повторного вызова.
const handleSubmit = async (event) => {
event.preventDefault(); // предотвращает отправку формы
const result = await signUpClientAction(new FormData(event.target)); // отправка данных
if (result.success) {
router.push("https://stackoverflow.com/");
}
};
return (
<form className="space-y-6" onSubmit={handleSubmit}>
{/* Ваши поля формы */}
</form>
);
2. Использование состояния
Вы можете установить состояние в компоненте для отслеживания успешного результата отправки. Это состояние можно использовать для условного рендеринга, чтобы избегать вызова нежелательной логики после успешного выполнения.
const [isSubmitted, setIsSubmitted] = useState(false);
const signUpClientAction = async (formData) => {
// Ваша логика отправки данных
if (response.success) {
setIsSubmitted(true); // отметка, что форма отправлена успешно
}
};
if (isSubmitted) {
return null; // или рендерьте другой компонент
}
3. Управление данными на стороне клиента
Перенос части логики в роутер или глобальное состояние (например, использование Redux или Context API) может помочь вам управлять состоянием и избежать вызова ненужных функций после перенаправления.
Заключение
Повторный вызов verifySignupToken
связан с особенностями серверного рендеринга в Next.js и динамической маршрутизации. С помощью управления состоянием, обработчиков событий и перенаправления вы можете минимизировать повторные вызовы и улучшить пользовательский опыт.
Эти рекомендации помогут вам оптимизировать вашу архитектуру приложения и повысить его производительность. Если у вас возникнут дополнительные вопросы или необходимость в дальнейшей помощи, пожалуйста, не стесняйтесь обращаться.