When using Next.js Server Actions to generate a token and then set a cookie on the client, calling router.refresh() immediately after may not always expose the cookie to subsequent server actions or server components.
'use server';
import {cookies} from 'next/headers';
import {defaultLocale} from './constants';
const baseFetch = async ({
endPoint,
method = 'GET',
options = {},
queryParams = {},
body,
url,
headers = {},
}) => {
try {
const appCookies = await cookies();
const publicToken = appCookies.get('token')?.value;
const locale = appCookies.get('NEXT_LOCALE')?.value || defaultLocale;
const queryString = new URLSearchParams(queryParams).toString();
const fullUrl =
url ||
`${process.env.NEXT_PUBLIC_API_URL}${endPoint}${
queryString ? `?${queryString}` : ''
}`;
const response = await fetch(fullUrl, {
method,
headers: {
...(publicToken && {Authorization: `Bearer ${publicToken}`}),
'Content-Type': 'application/json',
'Accept-Language': locale,
Accept: 'application/json',
'User-Agent': 'Next.js Server',
...headers,
},
...(body && {body: JSON.stringify(body)}),
cache: 'no-store',
...options,
});
const text = await response.text();
let json;
try {
json = text ? JSON.parse(text) : null;
} catch {
json = {error: text};
}
if (!response.ok) {
return {
...json,
status: response?.status,
headers: Object.fromEntries(response.headers.entries()),
endPoint,
};
}
return {
data: json,
headers: Object.fromEntries(response.headers.entries()),
};
} catch (err) {
return {
error: err?.message || 'Unexpected error occurred',
};
}
};
export default baseFetch;
'use client';
import {generatePublicToken} from '@/services';
import {setCookie} from 'cookies-next';
import {useEffect, useState} from 'react';
import useRouter from './useRouter';
import useHandleAnalytics from './useHandleAnalytics';
import {useDispatch, useSelector} from 'react-redux';
import {getPublicToken} from '@/selectors/auth';
import {setPublicToken} from '@/slices';
import {ONE_MONTH_IN_SECONDS} from '@/lib';
const useHandlePublicToken = () => {
const publicToken = useSelector(getPublicToken);
const [isCreatingPublicToken, setIsCreatingPublicToKen] = useState(false);
const dispatch = useDispatch();
const [createPublicTokenError, setIsCreatePublicTokenError] = useState(null);
const router = useRouter();
const {logException} = useHandleAnalytics();
const onCreatePublicToken = async () => {
setIsCreatingPublicToKen(true);
setIsCreatePublicTokenError(null);
const publicTokenRequest = await generatePublicToken();
if (publicTokenRequest?.data?.access_token) {
dispatch(setPublicToken(publicTokenRequest?.data?.access_token));
setCookie('token', publicTokenRequest?.data?.access_token, {
maxAge: ONE_MONTH_IN_SECONDS,
});
router.refresh();
} else {
setIsCreatePublicTokenError({
error: publicTokenRequest?.error || publicTokenRequest?.errors?.[0],
requestId: publicTokenRequest?.headers?.['x-request-id'],
});
logException({
description:
publicTokenRequest?.error || publicTokenRequest?.errors?.[0],
requestId: publicTokenRequest?.headers?.['x-request-id'],
endpoint: publicTokenRequest?.endPoint,
});
}
setIsCreatingPublicToKen(false);
};
useEffect(() => {
if (!publicToken && !isCreatingPublicToken) {
onCreatePublicToken();
}
}, [publicToken]);
return {
isCreatingPublicToken,
createPublicTokenError,
onRetry: onCreatePublicToken,
};
};
export default useHandlePublicToken;
'use server';
import baseFetch from '@/lib/baseFetch';
export const generatePublicToken = async () => {
const response = await baseFetch({
endPoint: `oauth/token`,
method: 'POST',
body: {
grant_type: 'client_credentials',
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
},
});
return response;
};
then if this hook called
useEffect(() => {
if (
!cartId &&
storeId &&
publicToken &&
!isCreatingOrder &&
!creatingOrderError
) {
handleNewOrderCreation();
}
}, [cartId, storeId, publicToken, isCreatingOrder]);
const handleNewOrderCreation = async () => {
clearOrderCookies();
const response = await onCreateOrder();
if (response?.error || response?.errors) {
logException({
description: response?.error || response?.errors?.[0],
requestId: response?.headers?.['x-request-id'],
endpoint: response?.endPoint,
});
} else {
router.refresh();
}
};
const onCreateOrder = async () => {
const storeId = getCookie('storeId');
const tableId = getCookie('tableId');
setIsCreatingOrder(true);
setCreatingOrderError(null);
const storeResponse = await getStore(storeId);
if (storeResponse?.error || storeResponse?.errors) {
setCreatingOrderError({
error: storeResponse?.error || storeResponse?.errors?.[0],
requestId: storeResponse?.headers?.['x-request-id'],
});
setIsCreatingOrder(false);
return storeResponse;
}
const deliveryMethodId =
storeResponse?.data?.stores?.delivery_methods?.[0]?.id;
const orderData = {
store_id: storeId,
delivery_method_id: deliveryMethodId,
...(tableId && {table_id: tableId}),
};
const response = await createCart(orderData);
if (response?.error || response?.errors) {
setCreatingOrderError({
error: response?.error || response?.errors?.[0],
requestId: response?.headers?.['x-request-id'],
});
logException({
description: response?.error || response?.errors?.[0],
requestId: response?.headers?.['x-request-id'],
endpoint: response?.endPoint,
});
} else {
setCookie('cartId', response?.data?.orders?.id, {
maxAge: ONE_MONTH_IN_SECONDS,
});
setCookie('cartStoreId', response?.data?.orders?.store?.id, {
maxAge: ONE_MONTH_IN_SECONDS,
});
setCookie('guest_token', response?.data?.orders?.guest_token, {
maxAge: ONE_MONTH_IN_SECONDS,
});
}
setIsCreatingOrder(false);
return response;
};
export const createCart = async body => {
const response = await baseFetch({
endPoint: `api/v1/orders/create_cart`,
method: 'POST',
body: {...body, source: process.env?.CLIENT_SOURCE},
});
return response;
};
so baseFetch when getting token from cookies sometimes it’s undefined for random users