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
Related
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.
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)
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
This component is for counting views at page level in Next.js app deployed on AWS Lambda
function ViewsCounter({ slug }: { slug: string }) {
const { data } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, { method: "POST" })
.catch(console.log);
registerView();
}, [slug]);
return (
<>
{views}
</>
);
}
This one is for displaying views on homepage
function ViewsDisplay({ slug }: { slug: string }) {
const { data } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
return (
<>
{views}
</>
);
}
While it works as expected on localhost, looks like it displays only the first fetched value and doesn't revalidate it for some reason.
When visiting the page, Counter is triggered correctly and the value is changed in DB.
Probably it has something to do with mutating, any hints are appreciated.
useSWR won't automatically refetch data by default.
You can either enable automatic refetch using the refreshInterval option.
const { data } = useSWR(`/api/views/${slug}`, fetcher, { refreshInterval: 1000 });
Or explicitly update the data yourself using a mutation after the POST request to the API.
function ViewsCounter({ slug }: { slug: string }) {
const { data, mutate } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, { method: "POST" })
.then(() => {
mutate();
})
.catch(console.log);
registerView();
}, [slug]);
return (<>{views}</>);
}
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.