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

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?

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

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

How to generate the list of route segment parameters for every pokemon using generateStaticParams in Next.js 13?

I am trying to use Nextjs 13's generateStaticParams to render static data into my web page. How do I generate the list of routes like /pokemon/[pokemonName] for every pokemon name using the generateStaticParams without providing hard-coded pokemon name (for example here I put "charmander" as argument for fetchData and then it generate route just for charmander)?
export const generateStaticParams = async (): Promise<PageParams[]> => {
const res = await fetchData("charmander");
return [
{
pokemonName: res?.data.pokemon.name,
},
];
};
Fetching pokemon moves from graphQL-pokeAPI:
const fetchData = async (pokemonName: string) => {
const POKEMON_MOVES = `
query pokemon($name: String!) {
pokemon(name: $name) {
id
name
sprites {
front_default
}
moves {
move {
name
}
}
}
}
`;
const res = await fetch("https://graphql-pokeapi.graphcdn.app/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: POKEMON_MOVES,
variables: { name: pokemonName },
}),
});
const moves = await res.json();
return moves;
};
export default function SpecificPokemon({ params }) {
const { pokemonName } = params;
const pokemonData = use(fetchData(pokemonName));
return (<h1>{pokemonName}</h1>...)
};
In the Nextjs13 beta docs, it said that generateStaticParams doesn't require any context parameters. So I can't pass pokemonName into generateStaticParams like this, right?
export const generateStaticParams = async (pokemonName: string) => {
const res = await fetchData(pokemonName);
I tried to just write fetchData("") and the page just blank. Also, it would be too many to write like this:
return [
{ pokemonName: "bulbasaur" },
{ pokemonName: "ivysaur" },
{ pokemonName: "venusaur" },
];
Also, is this due to my lack of JS / Next13 concept understanding?
They actually have a feature called getStaticPaths which was specifically designed for this. You can easily make a call to a pokemon api and parse the response into getStaticPaths at run to generate the static paths you want to use. I encourage you to play with the documentation more, that team did a killer job with them.
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
})
);
}
Here's an example for a posts table - but basically you get all the posts or in your case Pokemon, and map the names to an array.
I'm not sure if there's a better way of handling it but that should point you in the right direction. I'm still reading up on NextJS and especially the new Beta myself, so take with a pinch of salt.

Next.js keeps generating 404 page instead of blog details page

I am trying to generate my blog details page but next keeps giving me the 404 page. I am fetching the data from a headless cms called contentful. Here is what my file structure and code looks like
And this is my [path]s.js page
import {createClient} from "contentful"
const client = createClient({
space: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
accessToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
});
export const getStaticPaths = async () => {
const res = await client.getEntries({
content_type: "blog"
})
const paths = res.items.map(item => {
return {
params: {slug: item.fields.slug}
}
})
return {
paths,
fallback: false
}
}
export const getStaticProps = async ({params}) => {
const {items} = await client.getEntries({
content_type: "blog",
"fields.slug": params.slug
})
return {
props: {blog: items[0]}
}
}
function blogDetails({blog}) {
console.log(blog)
return (
<div>
Blog details
</div>
)
}
export default blogDetails
I do not understand where I am going wrong. What could be my problem?.
Firstly, you cannot use the square brackets as a "wildcard" at the beginning of the path and add some other characters after. So you should rename your file as just [path].js (without the s after [path]).
Secondly, in your getStaticPaths function, when you return the object of params inside paths, name the keys as the dynamic path you've named your file as. In your case, the object you return should be params: {path: item.fields.slug}. Alternatively, you can rename your file as [slug].js and leave the code like it is.
// [path].js
export const getStaticPaths = async () => {
const res = await client.getEntries({
content_type: "blog"
})
const paths = res.items.map(item => {
return {
params: {path: item.fields.slug} //renamed the key as "path"
}
})
return {
paths,
fallback: false
}
}
You can read more about dynamic routes and getStaticPaths() in the official documentation.

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