Proper way to change out a static page to an "expired page"? - reactjs

I have a bunch of static promotional pages. However, if the promo has passed (expired), i need to navigate/show an expired page. What is the correct way to do this with static pages in NextJS?
Attempt 1: do a check if expired in getStaticProps. Issue, revalidation happens every 600 seconds. So this could happen at 12:28am instead of 12:00am on the dot (depending on when i deployed it).
So it isn't showing an expired page on time. How to fix this issue? Or implement the 'proper' way to change out the page.
export const getStaticPaths = async () => {
const pageSlugs = await api.getAllSlugs();
const paths = pageSlugs.map((slug) => (params: {promo: slug})
);
return {
paths,
fallback: "blocking"
};
};
export async function getStaticProps({ preview = false, params, previewData }) {
const page = await api.getSlug(params.promo, {
preview,
enviroment: previewData?.enviroment
});
const isExpired = checkIfExpired(page.promoStart, page.promoEnd);
const expiredPage =
isExpired
? await api.getPage("expired-page", {
preview,
enviroment: previewData?.enviroment
})
: null;
return {
props: {
page,
isExpired,
expiredPage,
},
revalidate: 600
};
}

You could calculate the revalidate time dynamically:
export async function getStaticProps({ preview = false, params, previewData }) {
const page = await api.getSlug(params.promo, {
preview,
enviroment: previewData?.enviroment
});
const isExpired = checkIfExpired(page.promoStart, page.promoEnd);
const expiredPage =
isExpired
? await api.getPage("expired-page", {
preview,
enviroment: previewData?.enviroment
})
: null;
let revalidate = 600
if (Date.now() <= page.promoStart) {
revalidate = (page.promoStart - Date.now()) / 1000
} else if (date.now() > page.promoStart && Date.now() <= page.promoEnd) {
revalidate = (page.promoEnd - Date.now()) / 1000
}
return {
props: {
page,
isExpired,
expiredPage,
},
revalidate
};
}
This assumes promoEnd and promoStart are date objects but you can adjust as needed. Also make sure server time aligns with the timezone used on the date object.

Related

getStaticProps runs on every request in production

I use Next JS in my project. I want do only one request in page. And in next docs says that using get Static Props is that's what I need. But in doesnt work for me.
This is my code
export async function getStaticPaths() {
const leaguesCol = await collection(database, 'leagues');
const leaguesSnapshot = await getDocs(leaguesCol);
const leagues = leaguesSnapshot.docs.map(doc => doc.data());
return {
paths: leagues.map((item) => ({
params: {
id: item.link,
},
})),
fallback: false,
};
}
export async function getStaticProps() {
const { id } = context.params;
const leaguesRef = await collection(database, 'highlights');
const q = query(leaguesRef, where('league', '==', id));
const leagueSnapshot = await getDocs(q);
const data = leagueSnapshot.docs.map(doc => doc.data());
return {
props: { data },
};
}
But, when i deploy project in Firebase, i see that request happens in every routing to page. For example, in screen my routing between "spain" and "germany" pages
enter image description here

Prerender a page but have the correct conditional page?

I would like to pre-render my Black Friday pages. But when it is expired, it should show an expired page instead.
The issue with the code below is that it is loading the Black Friday page, then the expired page (presumably because it takes longer). The end result is the user gets an expired page with mixed styling from the Black Friday page styling. How do I fix this?
export async function getStaticPaths() {
const blackFridaylugs = await ContentfulApi.getBlackFridaySlugs();
const paths = blackFridaySlugs.map((item) => ({
params: { slug: item.slug }
}));
return {
paths,
fallback: "blocking"
};
}
export async function getStaticProps({ preview = false, params, previewData }) {
const page = await ContentfulApi.getBlackFridayPromoBySlug(params.slug, {
preview,
enviroment: previewData?.enviroment
});
const expiredPage = await ContentfulApi.getPageContentBySlug(
"expired-page",
{
preview,
enviroment: previewData?.enviroment
}
);
const globalSettings = await ContentfulApi.getGlobalSettings({ preview });
return {
props: {
page,
expiredPage,
globalSettings
},
revalidate: 100
};
}
function BlackFridayPages({ page, expiredPage, globalSettings }) {
const blackFridayEndDate = new Date('2023-02-20') //Change this to test
const now = new Date();
const expired = blackFridayEndDate < now
if (expired) {
return (
<ExpiredPage expiredPage={expiredPage} />
);
}
return (
<>
<BlackFridayPage />
</>
); //no extra conditional because I want it pre-rendered. Am I not allowed to do this?
}
export default BlackFridayPages;
Replicate bug:
1- User loads bf page when not expired
2- let the page expired
3- refresh
4- User gets the expired page (but it is not styled correctly)

Conditional rendering page shows loads wrong page

I'm using react and nextjs for a project. Essentially, if it's past the expired date, the page should show an expired page, if not show the existing page.
What is happening is that:
1- I'm on the Black Friday page before it's expired
2- Time expires
3- I refresh
4- it loads the Black Friday page (incorrect, it should only load expired).
export async function getStaticPaths() {
const blackFridaylugs = await ContentfulApi.getBlackFridaySlugs();
const paths = blackFridaySlugs.map((item) => ({
params: { slug: item.slug }
}));
return {
paths,
fallback: "blocking"
};
}
export async function getStaticProps({ preview = false, params, previewData }) {
const page = await ContentfulApi.getBlackFridayPromoBySlug(params.slug, {
preview,
enviroment: previewData?.enviroment
});
const expiredPage = await ContentfulApi.getPageContentBySlug(
"expired-page",
{
preview,
enviroment: previewData?.enviroment
}
);
const globalSettings = await ContentfulApi.getGlobalSettings({ preview });
return {
props: {
page,
expiredPage,
globalSettings
},
revalidate: 100
};
}
function BlackFridayPages({ page, expiredPage, globalSettings }) {
if (!expired) {
return (
<ExpiredPage expiredPage={expiredPage} />
);
}
return (
<>
<BlackFridayPage />
</>
);
}
export default BlackFridayPages;
in function BlackFridayPages
I don't know why !expired condition showing ExpiredPage
expired variable does not exists and not throw exception so it will display incorrect

Nextjs App doesn't work in production only in dev mode, return 500 error from firebase

I have an app that works really well in dev mode and even in the build mode of Next js, the repo is there: https://github.com/Sandrew94/space-tourism.
i think the problems is where i get the access_token from firebase in getServerSideProps for strange reason in devMode works and in production don't.
I have follow this guide to get that results https://colinhacks.com/essays/nextjs-firebase-authentication
export const getServerSideProps: GetServerSideProps = async (context) => {
try {
const cookies = nookies.get(context);
const { token } = cookies;
const planetInfo = await fetchPlanetsInfo("destinations", token);
return {
props: {
data: planetInfo || [],
},
};
////////////////////////////////////////////////////////////////
} catch (e) {
context.res.writeHead(302, { Location: "/" });
context.res.end();
return {
redirect: {
permanent: false,
destination: "/",
},
props: {} as never,
};
}
};
or in the context
export const AuthContextProvider = ({ children }: Props) => {
const [user, setUser] = React.useState<any>(null);
React.useEffect(() => {
return auth.onIdTokenChanged(async (user) => {
if (!user) {
setUser(null);
nookies.set(undefined, "token", "", { path: "/" });
} else {
const token = await user.getIdToken();
setUser(user);
nookies.set(undefined, "token", token, { path: "/" });
}
});
}, []);
// force refresh the token every 10 minutes
React.useEffect(() => {
const handle = setInterval(async () => {
const user = auth.currentUser;
console.log(user);
if (user) await user.getIdToken(true);
}, 10 * 60 * 1000);
// clean up setInterval
return () => clearInterval(handle);
}, []);
return (
<AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>
);
};
it's so annoying this thing, it works good in devMode i don't know what's changes in production mode.
/////////////////////////////
UPDATE 1
I have done some tests, it seems like the cookie isn't set properly and return undefined ( i check with some console.log) also i get a warning maybe this is the problem
The "token" cookie does not have a valid value for the "SameSite" attribute. Soon cookies without the "SameSite" attribute or with an invalid value will be managed as "Lax". This means that the cookie will no longer be sent to third-party contexts. If the application depends on the availability of this cookie in this type of context, add the "SameSite = None" attribute. For more information on the "SameSite" attribute, see https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/SameSite

Use CSR in Next.js as fallback when SSR path is not available

So I have this blog, with dynamic url for posts (blog/[postId]). Posts are being added through keystone.js cms. It worked fine via CSR, but i came to a necessity to add proper meta open graph tags for each post (for correct share snippets).
I added getStaticProps and getStaticPaths to my post page, and now my meta tags are working fine, but now i get 404 when trying to access any post, that was added after build. Meaning, now i have to rebuild after each new post. My question is - is it possible to set Next to render component as CSR, if it fails to find path for it with getStaticPaths? I've tried to use try catch, but that didn't work. Now i came up with this concept of using container, but it does not seem to be the right way to use fallback. Or is it? I am fairly new to Next, so i am a bit confused.
const PostContainer = (props) => {
const [postData, setPostData] = React.useState(null);
const router = useRouter();
React.useEffect(() => {
if (router.query?.id) {
getPost();
}
}, [router.query]);
const getPost = async () => {
const postData = await getPost(`${router.query.id}`);
if (postData) {
setPostData(postData);
}
};
if (router.isFallback) {
return <Post data={postData}
metaProps={defaultMetaProps}
/>
}
return (
<Post data={postData}
metaProps={props}
/>
);
};
export const getStaticPaths = async () => {
const list = await getPosts();
if (list?.length) {
const paths = list.map((post) => {
return {
params: {
id: post.id,
},
};
});
return {
paths,
fallback: true,
};
}
return {
paths: [],
fallback: true,
};
};
export const getStaticProps = async ({ params }) => {
const { id } = params;
const post = await getPost(id);
const {title, description, previewImage} = post
const props = {
props: {
title,
description,
url: `https://sitename./blog/${id}`,
image: previewImage,
},
revalidate: 1,
};
return props
};
export default PostContainer;
And one more thing - do i understand correctly that if fallback option is true, it takes only one visit to the page by ANY user, after which new path is generated on the server permanently and is accessible for anyone who comes next, therefore fallback always fires just once for this first visit of first user?

Resources