I am using next.js 10 and have a [slug] page which creates dynamic pages from Contentful CMS.
I am changing the slug inside CMS and run next dev the old slug correctly returns 404 and the new slug works.
However when I build and run next start the old slug still returns a cached page, the new slug works properly.
I am returning revalidate 10 and have assumed the page should refresh after 10sec
export const getStaticProps: GetStaticProps<SlugRoutePageProps> = async ({
params,
}) => {
....
....
const pageData = await getPageData(params.slug)
if (pageData.total === 0) return { notFound: true }
return {
props: {
pageType: "DynamicPage",
pageProps: {
pageData,
},
revalidate: 10,
},
}
}
in getStaticPaths I have fallback: "blocking", also tried fallback: true with no difference.
Edit:
getPageData is a basic call to the contentful api - no caching
const getPageData = async (
slug: string,
): Promise<FetchPagesResult> => {
const client = createContentfulClient()
return client.getEntries<Page>({
content_type: "page",
"fields.slug": slug,
include: 5,
order: "-sys.updatedAt",
limit: 1,
})
}
Related
I have a dynamic route at the root of my pages folder in Next; like so src/pages/[page].js
This works great for anything that actually has a slug for example example.com/my-page will return the correct data for my-page.
I want to be able to access a slug named homepage when you're visiting the root of the website. So visiting example.com would actually return the page homepage.
How can this be done? Here's a stripped back version of my component:
import React from "react";
import { fetchAPI } from "../lib/api";
const Page = () => <p>Content...</p>;
export default Page;
export async function getStaticPaths() {
const pages = await fetchAPI("/pages");
return {
paths: pages.map((page) => ({
params: {
page: page.slug,
},
})),
fallback: false,
};
}
export async function getStaticProps({ params }) {
const pages = await fetchAPI(`/pages?slug=${params.page}`);
return {
props: { page: pages[0] },
revalidate: 1,
};
}
You can configure rewrites in the next.config.js file to map the / path to the /homepage destination path.
// next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/',
destination: '/homepage'
}
];
}
};
This will not change the visible URL (/) in the address bar.
In getStaticProps, your params.page will be undefined on your home page because you're using a dynamic route there, so you just need to handle that case:
export async function getStaticProps({ params }) {
const pageSlug = params.page ?? "homepage"
const pages = await fetchAPI(`/pages?slug=${pageSlug}`);
return {
props: { page: pages[0] },
revalidate: 1,
};
}
Update To Redirect 404 Pages to Homepage
OP - in your comment below, you said "...but if a user visited something like example.com/asdopmsaiond wouldn't it also show the homepage in this case instead of a 404". While my code above will give you the homepage slug when a user visits the home page, it seems you also want to display the home page when a user visits any page that doesn't truly exist. There are generally three ways to go about this:
Redirect to the home page on 404 (Preferred)
You can redirect the user to the home page if no page is found by checking your pages variable - if it doesn't have a valid pages[0] value, you'll know (assuming your api is set up well) that no page was found and you can then redirect to the home page:
export async function getStaticProps({ params }) {
const pageSlug = params.page ?? "homepage"
const pages = await fetchAPI(`/pages?slug=${pageSlug}`);
if(!pages || !pages.length > 0) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
return {
props: { page: pages[0] },
revalidate: 1,
};
}
I say that this is "preferred" because it will not result in SEO duplicate content penalties like the last option would, and doesn't result in any additional processing or api calls.
Redirect with 404.js (or 404.tsx)
You can create a 404.js (or 404.tsx) file which Next uses when it needs to display a 404 page. This file also takes getStaticProps which you can use to just redirect to the home page:
// 404.js
export default function FourOhFourPage() {
return null
}
export async function getStaticProps({ params }) {
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
Display the home page on any 404 url
You can also resubmit your api call with the homepage slug if no pages are found in your api call, and then display that homepage content. This would result in a second api call and you'd want to include a canonical url to your home page so you don't get hit with duplicate content penalties for SEO:
export async function getStaticProps({ params }) {
const pageSlug = params.page ?? "homepage"
// Changed `pages` from `const` to `let` so we can modify it
// if we don't get the desired result from the api call
let pages = await fetchAPI(`/pages?slug=${pageSlug}`);
if(!pages || !pages.length > 0) {
pages = await fetchAPI(`/pages?slug=homepage`);
}
return {
props: { page: pages[0] },
revalidate: 1,
};
}
I'm trying to understand when exactly does getStaticProps fetch the data.
is it on the build process or every time the website is been render to someone (every time someone visits my app)
if it's the first one, doesn't it make my app "static" meaning it won't update the data when the data on my DB is changed?
Thanks!!
I am going to sleep So I just copied below answer from NextJS Docs
Next.js allows you to create or update static pages after you’ve built your site. Incremental Static Regeneration (ISR) enables you to use static-generation on a per-page basis, without needing to rebuild the entire site. With ISR, you can retain the benefits of static while scaling to millions of pages.
To use ISR add the revalidate prop to getStaticProps:
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every 10 seconds
revalidate: 10, // In seconds
}
}
export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// We'll pre-render only these paths at build time.
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return { paths, fallback: 'blocking' }
}
Is there any why to show loading screen while fetching API data using getStaticProps in next js?
export async function getStaticProps() {
const data = await fetch(API_END_POINT);
return {
props: {
data
}
}
}
getStaticProps runs at build time so there is no need for the loading screen since the data will always be available (statically generated).
However, there is another option: getStaticPaths
In combination with fallback pages.
Basically there is a mix of static and server rendered pages (but there is no request and response objects)
// pages/posts/[id].js
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
// This function gets called at build time
export async function getStaticPaths() {
return {
// Only `/posts/1` and `/posts/2` are generated at build time
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
// Enable statically generating additional pages
// For example: `/posts/3`
fallback: true,
}
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return {
props: { post },
// Re-generate the post at most once per second
// if a request comes in
revalidate: 1,
}
}
export default Post
Just make sure to add getStaticPaths with fallback equal to true. See example below
export async function getStaticPaths() {
return {
paths: [],
fallback: true,
}
}
then in the component(example a post page component), add a check if router isFallback is true. See below example
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
I have a product page at /products/[slug].js
and I use Incremental Static Generation for a wordpress/graphql site:
export async function getStaticProps(context) {
const {params: { slug }} = context
const {data} = await client.query(({
query: PRODUCT_SLUG,
variables: { slug }
}));
return {
props: {
categoryName: data?.productCategory?.name ?? '',
products: data?.productCategory?.products?.nodes ?? []
},
revalidate: 1
}
}
export async function getStaticPaths () {
const { data } = await client.query({
query: PRODUCT_SLUGS,
})
const pathsData = []
data?.productCategories?.nodes && data?.productCategories?.nodes.map((productCategory) => {
if (!isEmpty(productCategory?.slug)) {
pathsData.push({ params: { slug: productCategory?.slug } })
}
})
return {
paths: pathsData,
fallback: true,
}
}
Everything works as expected except one thing. If I delete a product from wordpress which was previously published, NextJs serves the cached page instead of showing 404 - Not found page, and I think this is how it is supposed to work, meaning that if something isn't rebuilt, show the previous (stale) page.
But how can I completely remove the cache for a specific product which has been deleted and it is not fetched again from the PRODUCT_SLUGS query ?
I have read the fallback options: true, false, blocking but none of them seems to work.
Is there a solution to this, either a next.config.js configuration or another work around ?
So I ran into this same issue, although I am using GraphCMS. So here is what you need to do to fix:
export async function getStaticProps(context) {
const {params: { slug }} = context
const {data} = await client.query(({
query: PRODUCT_SLUG,
variables: { slug }
}));
if (!data) {
return {
notFound: true
}
} else {
return {
props: {
categoryName: data?.productCategory?.name ?? '',
products: data?.productCategory?.products?.nodes ?? []
},
revalidate: 1
}
}
}
You need to return notFound: true in getStaticProps
notFound - An optional boolean value to allow the page to return a 404 status and page.
See this page in the docs https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation
Then in getStaticPaths change fallback to fallback: "blocking". If you keep fallback: true it is going to serve the stale page since that built successfully.
I think this is possible starting from next#12.1.x using this feature On-demand Incremental Static Regeneration
https://nextjs.org/blog/next-12-1#on-demand-incremental-static-regeneration-beta
basically you can define an api path in this way
// pages/api/revalidate.js
export default async function handler(req, res) {
// Check for secret to confirm this is a valid request
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' })
}
const PRODUCT_SLUGS = req.query.product_slug;
try {
await res.unstable_revalidate(`/products/${PRODUCT_SLUGS}`)
return res.json({ revalidated: true })
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send('Error revalidating')
}
}
Using this api path you can invalidate the cache for a specific product
For example, I have a dynamic route /blog/[article-id].
When visiting an existing blog post /blog/id-that-exist, it works as expected, and now I want to handle the case /blog/id-that-does-not-exist properly.
The code in /blog/[id].jsx looks something like:
export const getStaticPaths async () => {
return {
fallback: true,
paths: (await sequelize.models.Article.findAll()).map(
article => {
return {
params: {
pid: article.slug,
}
}
}
),
}
}
export const getStaticProps async () => {
// Try to get it from the database. Returns none if does not exist.
const article = await sequelize.models.Article.findOne({
where: { slug: pid },
});
return { props: { article: article } };
}
const ArticlePage = (props) => {
// This can happen due to fallback: true while waiting for
// a page that was not rendered at build time to build.
const router = useRouter()
if (router.isFallback) {
return <div>loading</div>;
}
return (
<div>{props.article.body}</div>
);
};
export const getStaticPaths = getStaticPathsArticle;
export const getStaticProps = getStaticPropsArticle;
export default ArticlePage;
I saw this related question: How to handle not found 404 for dynamic routes in Next.js which is calling API? but I'm not sure if it's the same as I'm asking here, as this does not depend on any external API being used.
notFound: true from Next.js 10
Starting in Next.js 10, we can do:
export const getStaticProps async () => {
// Try to get it from the database. Returns none if does not exist.
const article = await sequelize.models.Article.findOne({
where: { slug: pid },
});
if (!article) {
return {
notFound: true
}
}
return { props: { article: article } };
}
as documented at: https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation
When notFound is returned, the rendering function ArticlePage just never gets called, and the default 404 page is returned instead.
Note however that ArticlePage did get
For some reason in development mode:
I don't get the expected 404 HTTP status code
ArticlePage, so if you forgot to handle the fallback case, the it might crash due to missing properties
which was confusing me a bit. But in production mode, everything works as expected.
Workaround before Next.js 10
As shown https://github.com/vercel/next.js/discussions/10960#discussioncomment-1201 you could previously do something like:
const ArticlePage = (props) => {
if (!props.article) {
return <>
<Head>
<meta name="robots" content="noindex">
</Head>
<DefaultErrorPage statusCode={404} />
</>
}
return (
<div>{props.article.body}</div>
);
};
but this is not ideal because it does not set the HTTP return code correctly I believe, and I don't know how to do it.
Tested on Next.js 10.2.2.
I've read your answer regarding the solution after Next.js v.10, but I didn't get what was the problem in showing the expected http 404 code during development.
I use Next.JS v.12 and I get the expected 404 normally in development
import { GetStaticPaths, GetStaticProps } from 'next'
import { useRouter } from 'next/router'
import { ParsedUrlQuery } from 'querystring'
import Loading from '../../components/loading'
export const getStaticPaths: GetStaticPaths = async () => {
//your paths
return { paths, fallback: true }
}
export const getStaticProps: GetStaticProps = async ({ params }: { params?: ParsedUrlQuery }) => {
//get your props
if (!target){
return {notFound: true}
}
return { props: { ... }, revalidate: 86400}
}
function Index({ ... }) {
const router = useRouter()
if (router.isFallback) {
return <Loading />
}
return (
<div>
//my content
</div>
)
}
export default Index
When the target isn't found, it renders my custom 404 component in pages/404.tsx if I created one or just the default 404 page.
This should work normally during development and production.