NextJS: unable to set title with dynamic routes - next.js

I am not able to set title and others meta tags on a page created with dynamic routes.
Title and metas are set correctly when the page is loaded in the browser, but they are not set in the raw HTML code I fetch using wget.
I have tried using both <Head> and <NextSeo> with same results.
I have also tried both getStaticProps() / getStaticPaths() and getServerSideProps() approaches.
Here is a simplified snippet of my code.
const PostPage = ( { post } ) => {
let txt = post.text || post.brief_descr;
return (
<Layout>
<NextSeo
title={post.title}
description={post.brief_descr}
/>
<h1>Hello World</h1>
</Layout>
);
};
export async function getServerSideProps ( { res, query } ) {
const p = query.params[ 0 ];
const data = await act_post_get( id, slug )();
return {
props: { post: data }
};
}

It turned out I was messing things up with the _document.tsx and the getInitialProps() function.
By simplifying the _document.tsx I was able to use the getStaticProps() with revalidate option correctly, and now it works as expected.

Related

Redirect to 404 when custom [slug] is not found in Next JS

I'm surprised I can't find this anyway but here is my issue.
I have a Next JS site with the path /location/[location].js
The page looks pretty basic
import { nodes } from '../../components/data/nodes'
export default function Location() {
const router = useRouter()
useEffect(() => {
//Do various things
}, [])
return (
<Layout>
...My website...
</Layout>
)
}
and nodes looks like this
export const nodes = [
{
id: 'Test1'
}, {
id: 'Test2'
}, {
id: 'Test3'
}]
So how can I say if my [location] slug does not match any node id's go to the 404 page? I tried some janky garbage that just feels wrong and throws console errors:
var counter = 1
for (var node of nodes) {
if (router.query.location == node.id) {
break
} else if (counter++ >= nodes.length) {
return <Error statusCode={404} />
}
}
Can someone help me work this out. Thanks
I'd prefer you to use getStaticProps & getStaticPaths In order to solve this problem. You can use the getStaticProps to fetch the static props. and to define what paths are valid you can use getStaticPaths and load the paths from your nodes variable. If the path doesn't exist you can then simply return a 404 error instead of props
Please refer the official document of Next.js to know more about the getStaticProps & getStaticPaths
You can easily define a state and according to state render your component.
import Error from "next/error";
// inside functional component
const [errorCode , setErrorCode] = useState(0);
const router = useRouter():
useEffect(() => {
const location = router.query.location
const search = nodes.filter(item => item.id === location ):
if(search.length === 0){
setErrorCode(404)
}
},[])
if (errorCode == 404) {
return (
<Error statusCode={errorCode} title="page Not Found" />
</>
);
}
return (
<Layout>
...My website...
</Layout>
)

Next.js getStaticProps/getServerSideProps best use case

I have a blog that is using getStaticProps + getStaticPaths to generate pages from headless WP - all good. Then I have a component that is connecting to the database to allow me to link to the next post in any within any individual category. This component is then used inside the ...postSlug page.
So basically the component works perfectly if I switch to using getServerSideProps for posts, as it updates the 'previous' + 'next' URLs as each post is requested and built on the server. However, if I use getStaticProps the component doesn't update with the correct URLs unless I manually refresh the page. So I need to use some client-side data fetching within the component, with useState, useEffect or SWR or something - but I'm not sure about the best way to do it (or even if that would work or if I should just use gServerSideProps).
Any help would be greatly appreciated. Thanks! Component below edited for clarity ...
export default function MovePost() {
const { usePost } = client
const post = usePost()
const { query = {} } = useRouter()
const { categorySlug } = query
const currentPaginationCursor = btoa( `arrayconnection:${post.databaseId}` )
const { posts } = client.useQuery()
const previous = posts({
first: 1,
before: currentPaginationCursor,
where: { categoryName: `${categorySlug}` },
}).nodes[0]?.slug
return (
<>
{ previous &&
<Link href={`/archive/${categorySlug}/${previous}`}>
<a ...
export default function Page() {
const { usePost } = client
const post = usePost()
return (
<>
<Layout>
<div dangerouslySetInnerHTML={{ __html: post?.content() ?? '' }} />
<MovePost />
</Layout>
</>
)
}
export async function getStaticProps(context: GetStaticPropsContext) {
return getNextStaticProps(context, {
Page,
client,
notFound: await is404(context, { client }),
})
}
export function getStaticPaths() {
return {
paths: [],
fallback: 'blocking',
}
}

How to provide all route params to getStaticPaths?

I am trying to create a dynamic page component [page].js that can display pages dynamically. The route is set dynamically to
localhost:3000/[channel]/[store]/page/[slug]
I provided slug as params in paths array in getStaticPaths but I'm getting the following error:
Server Error
Error: A required parameter (channel) was not provided as a string in getStaticPaths for /[channel]/[store]/page/[slug]
This error happened while generating the page. Any console logs will be displayed in the terminal window.
It looks like it's asking for channel and store params?
I do have channel and store data in getStaticProps but not in getStaticPaths.
I've set up getStaticPaths with slug param but I'm not sure how to find out channel and store params here.
import {fetchLayout} from "../../../../http/server/fetchLayout";
import Layout from "../../../../views/layout/Layout/Layout";
import Head from "../../../../resources/components/Head/Head";
import {fetchPage} from "../../../../http/server/fetchPage";
import {fetchPages} from "../../../../http/server/fetchPages";
const Page = ({ layout, page }) =>
{
if ( page && layout ) {
const { title, meta_title, description, keywords } = page
return (
<Layout data={ layout }>
<Head title={ meta_title } >
<meta name="description" content={ description } />
<meta name="keywords" content={ keywords } />
</Head>
<h1 style={{ textAlign: `center`, margin: `50px 0` }}>{ title }</h1>
</Layout>
)
}
return null
}
export const getStaticProps = async ({ params }) =>
{
const layout = await fetchLayout()
// const { channel, store } = layout.region
const page = await fetchPage( params.slug )
return { props: { layout, page }}
}
export const getStaticPaths = async () =>
{
const pages = await fetchPages()
const paths = pages.map(({ slug }) => ({ params: { slug }}))
return { paths, fallback: true }
}
export default Page

NextJS - Categories (Tags) - Dynamic Pages

I'm using Next.js with Static Site Generation, and I've created a category (tags) component as shown below:
import Link from "next/link";
export default function CategorySection({ categories }) {
return (
<Wrapper>
<ContentWrapper>
<CategoryWrapper>
{categories.map((category) => {
return (
<>
{category.contentfulMetadata.tags.map((tag) => {
return (
<Link href={`/articles/categories/${tag.id}`}>
<Categories>{tag.name}</Categories>
</Link>
);
})}
</>
);
})}
</CategoryWrapper>
</ContentWrapper>
</Wrapper>
);
}
I would like to be able to create dynamic pages for all tags and show articles only related to the tag.
I have created my [slug].jsx file at the following location: /pages/articles/categories/[slug].jsx
[slug].jsx file below:
import { getArticles, getArticle } from "../../../utils/contentful";
export async function getStaticPaths() {
const data = await getArticles();
return {
paths: data.articleCollection.items.map((article) => ({
params: { slug: article.contentfulMetadata.tags.id },
})),
fallback: false,
};
}
export async function getStaticProps(context) {
const data = await getArticle(context.params.slug);
return {
props: { article: data.articleCollection.items[0] },
};
}
export default function Category({ article }) {
return <h1>{article.contentfulMetadata.tags.name}</h1>;
}
I get the following error when navigation by using the tags on my articles page:
Error: A required parameter (slug) was not provided as a string in getStaticPaths for /articles/categories/[slug]
How can I get it to create dynamic pages using the tags?
Error: A required parameter (slug) was not provided as a string in getStaticPaths for /articles/categories/[slug]
In your getStaticPaths you need to convert slug to string.
params: { slug: article.contentfulMetadata.tags.id.toString() }

Reactjs custom hook call infinite loop

I am creating my first ever self made web development project and have run into an infinite loop. The full project can be found on https://github.com/Olks95/my-dnd/tree/spellbook. So the question is: What causes the loop and how do I fix it?
(The loop happens somewhere in the 2nd item of the 'Playground' component when the ContentSelector - Spellbook is called. The custom hook useHandbook is called in Spellbook and continously calls the API, should obviously only happen once... refresh or click return to stop spamming )
From what I can tell the issue is not in the custom hook itself, as I have made several attempts to rewrite it and an empty dependency array is added to the end of the useEffect(). I will try to explain with example code here.
import { Component1, Component2, Component3 } from './ContentSelector.js';
const components = {
option1: Component1,
option2: Component2
option3: Component3
}
const Playground = (props) => {
const LeftItem = components['option1']
const MiddleItem = components['option2']
const RightItem = components['option3']
...
}
I wanted to be able to choose what content to put in each element and ended up making a ContentSelector component that has all the content components in one file, and individually imported/exported. This seems like a strange way to do it, but it was the only way I found to make it work. (Maybe the cause of the loop?) Since this is still fairly early on in the development the selection is hard coded. The item variables starts with a capital letter so I can later call them as components to render like so:
<LeftItem ...some properties... />
Playground then returns the following to be rendered:
return(
<React.Fragment>
<div className="container">
<div className="flex-item">
/* Working select-option to pass correct props to Component1 */
<div className="content">
<LeftItem ...some properties... />
</div>
</div
<div className="flex-item">
/* Currently the same selector that changes the content of the LeftItem */
<div className="content">
<MiddleItem ...some properties... />
</div>
</div>
/*RightItem follows the same formula but currently only renders "coming soon..." */
</div>
</React.Fragment>
)
The Content selector then has the three components where:
Component1: calls a custom hook that only runs once. The information is then sent to another component to render. All working fine.
Component2: calls a custom hook infinite times, but is expected to work the same way component 1 does...
Component3: Renders coming soon...
See Component1 and 2 below:
export const Component1 = (props) => {
const [ isLoading, fetchedData ] = useDicecloud(props.selectedChar);
let loadedCharacter = null;
if(fetchedData) {
loadedCharacter = {
name: fetchedData[0].Name,
alignment: fetchedData[0].Alignment,
/* a few more assignments */
};
}
let content = <p>Loading characters...</p>;
if(!isLoading && fetchedData && fetchedData.length > 0) {
content = (
<React.Fragment>
<Character
name={loadedCharacter.name}
alignment={loadedCharacter.alignment}
/* a few more props */ />
</React.Fragment>
)
}
return content;
}
export const Component2 = (props) => {
const [ fetchedData, error, isLoading ] = useHandbook('https://cors-anywhere.herokuapp.com/http://dnd5eapi.co/api/spells/?name=Aid')
let content = <p>Loading spells...</p>;
if(!isLoading && fetchedData) {
/* useHandbook used to return text to user in a string in some situations */
if(typeof fetchedData === 'string') {
content = (
<React.Fragment>
<p> {fetchedData} </p>
</React.Fragment>
)
} else {
content = (
<React.Fragment>
<Spellbook
/* the component that will in the future render the data from the API called in useHandbook */
/>
</React.Fragment>
)
}
}
return content;
}
I have been working on this issue for a few days and it is getting more confusing as I go along. I expected the mistake to be in useHandbook, but after many remakes it does not seem to be. The current useHandbook is very simple as shown below.
export const useHandbook = (url) => {
const [ isLoading, setIsLoading ] = useState(false);
const [ error, setError ] = useState(null);
const [ data, setData ] = useState(null);
const fetchData = async () => {
setIsLoading(true);
try {
const res = await fetch(url, {
method: "GET",
mode: 'cors'
});
const json = await res.json();
setData(json);
setIsLoading(false);
} catch(error) {
setError(error);
}
};
useEffect(() => {
fetchData();
}, []); //From what the documentation says, this [] should stop it from running more than once.
return [ data, error, isLoading ];
};
EDIT: I ran the chrome developer tools with the react extension and saw something that might be useful:
Image showing Component2 (Spellbook) run inside itself infinite times
You can modify your custom hook useHandbook's useEffect hook to have URL as dependency, since useEffect is similar to componentWillMount, componentDidUpdate and componentWillUnmount, in your case it is componentDidUpdate multiple times. So what you can do is.
useEffect(() => {
fetchData();
}, [url]);
Since there is no need to fetch data agin unless URL is changed
I found the mistake. Component2's real name was Spellbook which I had also called the rendering component that I had not yet made. It turns out I was calling the component from within itself.
Easy to see in the image at the edit part of the question.

Resources