Prerender a page but have the correct conditional page? - reactjs

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)

Related

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

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.

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

My dynamic routing isn't displaying any information, I just get 404. next.js react

I am having issues with dynamic routes in Next.js.
Setup :
I have getStaticPaths and getStaticProps in my dynamic page. When I click on an item that should take me to the dynamic route, the browser navigates to a page with the right url "properties/propertyId" ; However, the page says 404, the page could not be found.
I am not sure if my problem is how I am fetching my data from firebase . I tried using the data fetching method on another page and it does fetch the data, so I am not 100% sure where the problem is.
When I inspect the network tab, it says 404 not found
http://localhost:3000/Properties/5f328e9c-656f-4691-8b4b-4f414bbfdee2
the path is correct, it matches whats in the database, but the webpage says 404
Here is my dynamic page , I believe the getStaticPaths portion is ok, but a second opinion would be nice, however regarding the getStaticProps, I think the problem is there.
import { get, ref } from "firebase/database";
import React, { useState } from "react";
import BookingMenu from "../../components/BookingMenu";
import { database } from "../../components/firebase";
import PropertyStyles from "../../styles/ViewProperty.module.css";
const ViewProperty = ({ property }) => {
return (
<>
<div style={{ display: "flex" }}>
<div className={PropertyStyles.album}>Property id</div>
<BookingMenu />
</div>
</>
);
};
export default ViewProperty;
export const getStaticPaths = async () => {
const propertyRef = ref(database, "properties");
const allProperties = [];
const getProperties = async () => (await get(propertyRef)).val();
getProperties().then((properties) => {
allProperties.push[properties ? Object.values(properties) : []];
});
console.log(allProperties);
const paths = allProperties.map((property) => {
return {
params: { Id: property.Id.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context) => {
const id = context.params.Id;
const propertyRef = ref(database, "properties/" + id);
const property = [];
const getProperties = async () => (await get(propertyRef)).val();
getProperties().then(
(prop) => property.push[prop ? Object.values(prop) : []]
);
return {
props: { property: property },
};
};
pages folder structure
use id insted of Id
const paths = allProperties.map((property) => {
return {
params: { id: property.Id.toString() },
};
});
...
const id = context.params.id;
I changed fallback in getStaticPaths to true.
return {
paths,
fallback: true,
};
Try to visit http://localhost:3000/properties/5f328e9c-656f-4691-8b4b-4f414bbfdee2 - "properties" in lowercase. If you are using Mac OS this problem is possible, because there is case insensitive file system, for example you name folders pages/Properties but for Mac OS it is the same as pages/properties and nextJs will generate /properties

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?

NEXT.js ISR remove static page

I have a little problem with the ISR. I have the revalidate prop equal 1s like here
export async function getStaticProps({ params }) {
const data = await client.getEntries({
content_type: "product",
"fields.name": params.slug,
});
if (!data.items[0]) {
return {
notFound: true,
};
}
return {
props: {
article: data.items[0],
revalidate: 1,
},
};
}
When I create product in Contentful, the page is created as I expected. When I want to entry to page that doesn't exist I get 404 error as expected. Problem starts when I change something in Contentful in existing product or I delete it.
When I delete the product in Contentful, the list of products in the products page is updated and the product disappears but I can still entry in page of that product. Also when I rename the product name the list of products is updated but I still can entry to the earlier page name.
Is there any solution to solve this problem?
getStaticPaths
export async function getStaticPaths() {
const data = await client.getEntries({
content_type: "product",
});
return {
paths: data.items.map((item) => ({
params: { slug: item.fields.name },
})),
fallback: true,
};
}
Product page
const Article = ({ article }) => {
const router = useRouter();
if (router.isFallback) return <p>Loading...</p>;
return (
<div>
<h1>Hello! {article.fields.name}</h1>
<Link href="/about">
<a>Back to about!</a>
</Link>
</div>
);
};
EDIT
When I change product name from "product77" to "product7" in Contentful after revalidate static page in my build for product77 still exist and I still can entry to that page.
Shouldn't it be removed after revalidation?
Removed pages can't be deleted, but served with a 404 instead using On-demand Revalidation, if getStaticPaths and getStaticProps are configured accordingly.
// /pages/blog/[...post].jsx
function Post({ postData }) {
return (
// ...
);
}
export async function getStaticPaths() {
const res = await fetch("https://.../posts");
const posts = await res.json();
// Prerender paths during build
const paths = posts.map((post) => ({
params: { post: post.id },
}));
return { paths, fallback: "blocking" };
}
export async function getStaticProps(context) {
const res = await fetch(`https://.../posts/${context.params.post}`);
const postData = await res.json();
if(!postData){
// The page doesn't exist in the CMS, return notFound to trigger a 404
return{
notFound: true,
revalidate: 30
}
}
// Return page props
return {
props: {
postData,
},
revalidate: 30,
};
}
The main takeaway from the above code is:
You can pre-render paths in getStaticPaths
getStaticPaths must use "blocking" or true as the fallback value
Handle unknown paths inside getStaticProps (thanks to fallback). If the page doesn't exist in the CMS, return notFound: true triggering the 404 page
Then, using the On-demand revalidation approach in a webhook:
// /pages/api/revalidate-post.js
export async function handler(req, res) {
try {
const reqBody = JSON.parse(req.body);
await res.revalidate(`/${reqBody.path}`);
return res.json({ revalidated: true });
} catch (err) {
return res.status(500).send("Error revalidating");
}
}
The res.revalidate(/${reqBody.path}) invokes a fresh evaluation of a page using the logic from getStaticProps.
Now, if somebody would delete the page in the CMS, and trigger the above revalidation webhook handler for the deleted page path, then the path serves a 404 page instead of the original page.
The page itself, however, is not deleted from the disk.

Resources