My Next.js app's pages are provided by an API, each with a uri_prefix and a uri.
The uri_prefix is mandatory, and can be c or s for now. But any letter could be chosen in the end.
It's a pretty simple structure :
pages
|-[prefix]
| |
| |-[slug]
The [slug].jsx page that handles it uses ISG.
So far, the getStaticPath fallback was set to true, so that if an admin creates new pages (eg. {uri_prefix : c, uri : newpage1} & {uri_prefix : s, uri : newpage2}) in the backoffice, ISG generates the static /c/newpage1 and /s/newpage2 file when they are first triggered.
But once generated, trying alt url such as /x/newpage or /whatever/newpage also fires up the page, which is somewhat unexpected (and unwanted). Reading the doc, I thought it would allow only existing prefixes.
Setting fallback to false forbids unwanted urls but also requires a new build of the app, which is not convenient.
I'd like to have /c/newpage1 or /s/newpage2 rendered, but not /c/newpage2 nor /s/newpage1 (nor /somethingelse/newpage* of course). Each page associated with it's own prefix only.
Did I misunderstood how the fallback works ?
Is ther an obvious mistake in the code or is there another way to achieve ISG for new pages without whole build while forbidding unwanted urls ?
Here is the [slug].jsx page :
import Layout from '#/components/Layout';
import SmallCard from '#/components/SmallCard';
import {useRouter} from 'next/router';
export default function src({products, seo}) {
const router = useRouter();
if(router.isFallback) {
return (
<h1>Loading new page...</h1>
)
}
return (
products ? (
<Layout title={seo[0].title} description={seo[0].description}>
<div>
<div className='container-fluid my-5'>
<div className="row justify-content-center">
<h1>{seo[0].h1}</h1>
</div>
<div className="row justify-content-center">
{products.map(
(product)=>(
<SmallCard product = {product}/>
)
)}
</div>
</div>
</div>
</Layout>
) : (
<Layout title={seo[0].title} description={seo[0].description}>
<h1>{seo[0].h1}</h1>
<div dangerouslySetInnerHTML={{__html: seo[0].content_front}}/>
</Layout>
)
)
}
export async function getStaticPaths() {
const resPages = await fetch(`${process.env.API_BASE_URL}/path/to/pagesapi`);
const pages = await resPages.json();
const paths = pages.map((page) => ({
params: {
prefix: page.uri_prefix,
slug: page.uri
},
}))
return {
paths,
fallback: true
};
}
export async function getStaticProps({ params: { slug } }) {
const resPages = await fetch(`${process.env.API_BASE_URL}/path/to/pagesapi`);
const pages = await resPages.json();
const seo=pages.filter(page=> page.uri == slug);
if(seo[0].src) {
const src=seo[0].src;
// get products
const resProducts = await fetch(`${process.env.API_BASE_URL_LEGACY}${src}`);
var products = await resProducts.json();
} else {
var products = null
}
return {
props: {
products,
seo
},
revalidate:60,
};
}
Thanks in advance.
Related
In my Next js app I'm Passing an Object through pages. what I did is compress my array of objects into JSON JSON.stringify(result) from index page and in my second page I parsed it JSON.parse(props.router.query.result). this worked great. but the issue is when reloading the page the browser prompts
This page isn’t workingIf the problem continues, contact the site owner.
HTTP ERROR 431
I know this message indicates for long url head. so is there a way for me to shorten this?
index page
<Link href=
{{pathname: "/tv-shows",
query: {
result: JSON.stringify(result),
//result: [result],
img: img,
index: index,
}}} key={index}
>
<div className=' relative snap-center h-56 w-96 rounded-3xl hover:rounded-3xl hover:scale-110 hover:transition-all hover:duration-200 hover:ease-in ease-out duration-200 '>
<Image
src={img}
layout="fill"
objectFit="cover"
className={`h-full w-full bg-cover bg-no-repeat rounded-3xl hover:rounded-3xl hover:scale-110 hover:transition-all hover:duration-200 hover:ease-in ease-out duration-200`} />
</div></Link>
in second page
const TvShows = (props) => {
const [result, setResult] = useState([]);
const [index, setIndex] = useState("");
const [img, setImg] = useState("");
useEffect(()=>{
console.log("first")
console.log(props.router.query);
if (props.router.query.result){
const query = props.router.query;
const res = JSON.parse(query.result);
setResult(res);
setIndex(query.index);
setImg(query.img);
//console.log(JSON.parse(props.router.query.result));
}
},[props.router.query ])
return (
<div className=''>
<Head>
<title>{Popular[Number(index)].title} | </title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Background result={result} img={img} index={index} />
{/* <Background img={img} index={index} /> */}
<div className='cursor-default px-10'>
<div className=' text-xl pt-5 pb-5'>Full Episodes </div>
{/* <Contents result={result} index={index}/> */}
</div>
</div>
)
}
export default withRouter(TvShows)
please help me with the fix
Based on comments to your original post, I deducted that you do not want to shorten a very long URL, but you are trying to pass data between subpages of Next app and save it so it is accessible after page refresh. What you can do to solve your issue is saving your result to localStorage every time it changes:
useEffect(() => {
localStorage.setItem("result", JSON.stringify(result))
}, [result])
And then, in your second page read the data:
useEffect(()=>{
const result = JSON.parse(localStorage.getItem("result"))
console.log("first")
console.log(result);
if (result){
setResult(result);
setIndex(query.index);
setImg(query.img);
}
}, [])
After comments to this Answer:
I think that what you want to do is creating a page tv-shows, which will display the details of one Youtube playlist. Best way to get this working is by creating dynamic routes.
Create the following directory structure in your app:
root
└── pages
└── tv-shows
└── [index].js
Paste this into the file [index].js
import { useRouter } from "next/router";
export async function getStaticPaths() {
return {
paths: [{ params: { index: "0" } }, { params: { index: "1" } }],
fallback: false
};
}
export async function getStaticProps(context) {
const MY_PLAYLIST_1 = "PL9562CjMJkXIgaV_UA5hf1VADfn4Sqd0P";
const MY_PLAYLIST_2 = "PL9562CjMJkXIgaV_UA5hf1VADfn4Sqd0P";
const API_KEY = "AIzaSyCELe0KoZYBjonJskBMbzdlTuCow3sr3zo";
const PLAYLIST_REQUEST_URL_1 = `https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=500&playlistId=${MY_PLAYLIST_1}&key=${API_KEY}`;
const PLAYLIST_REQUEST_URL_2 = `https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=500&playlistId=${MY_PLAYLIST_2}&key=${API_KEY}`;
const playlistResponse1 = await fetch(PLAYLIST_REQUEST_URL_1);
const playlistResponse2 = await fetch(PLAYLIST_REQUEST_URL_2);
const playlistResult1 = await playlistResponse1.json();
const playlistResult2 = await playlistResponse2.json();
return {
props: {
results: [playlistResult1, playlistResult2],
},
revalidate: 3600,
};
}
export default function TvShows({results}) {
const router = useRouter();
const { index } = router.query;
return <div>{index}, {JSON.stringify(results[index])}</div>;
}
Next step, override content of card.js with the following (just remove result variable and the query parameter)
import Link from "next/link";
const Card = ({ index }) => {
return (
<nav>
<Link
href={`/tv-shows/${index}`}
>
<div>
<h1>card click</h1>
</div>
</Link>
</nav>
);
};
export default Card;
Override index.js to remove unnecessary API calling code and take new card.js's props into account:
import Link from "next/link";
import Card from "../comps/card";
import Popular from "../comps/popular";
export default function IndexPage() {
return (
<div>
Hello World. {/* <Link href="/about">
<a>About</a>
</Link> */}
<Card index={0} img={Popular[0].img} />
<Card index={1} img={Popular[1].img} />
</div>
);
}
How the solution works is as follows:
We create dynamic routes which takes only query parameter index of our playlist. Every index parameter that is possible to be set is defined in paths: [{ params: { index: "0" } }, { params: { index: "1" } }]. These path params are then passed to our dynamic route which is then pre-rendered, and downloading all the data only once. And finally, our route displays data based on query parameters supplied by useRouter.
I'm having some trouble with a dynamic route in Next JS, and navigating from one dynamic page to another.
I have a custom type called 'projects'. In that custom type I have set up a Content Relationship field from Prismic called 'relatedProject', so I can select another project in the CMS as a linked document at the end of each project page.
Displaying this related project works fine, and is displaying the correct information when I navigate to a project page, but when I click the link to take me to the related project document - which is the same dynamic route, just with different content, the content is not changing to the content that should be getting pulled in from the new project. A proper load doesn't occur as the page load animations are not playing when clicking the link. When I perform a hard refresh the page is rendering correctly with the content of the project page I clicked through to.
I'm unsure whether this is something wrong with Prismic or something wrong with Next JS? Apologies if I have not explained this brilliantly. I am sure getStaticPaths is setup correctly, as I can navigate to any other project page from the home or work pages, just not from one project page to another. Really has got me stumped!
This is my code for the dynamic page template [uid].js, any help is appreciated:
import { useEffect, useRef } from 'react';
import { createClient } from '../../prismic';
import resolver from '../../sm-resolver.js';
import * as prismicH from '#prismicio/helpers';
import { linkResolver } from '../../utils/linkResolver';
import { SliceZone } from '#prismicio/react';
import { gsap } from 'gsap';
import { Layout } from '../../components/Layout';
import ProjectHero from '../../components/Hero/Project';
import RelatedProjectCta from '../../components/RelatedProjectCta';
const ProjectDetail = ({ data, url, lang, layout }) => {
const seo = {
metaTitle: data.metaTitle || layout.metaTitle,
metaDescription: data.metaDescription || layout.metaDescription,
metaImage: data.metaImage?.url || layout.metaImage?.url,
url: url,
article: true,
lang: lang,
};
const pageData = { data };
const relatedProject = { data };
const revealOverlay = useRef();
// Hero reveal
useEffect(() => {
gsap.to(revealOverlay.current, {
opacity: 0,
duration: 2.3,
ease: "power2.out"
});
}, []);
return (
<Layout seo={seo} {...layout}>
<ProjectHero {...pageData} />
<SliceZone slices={data.slices} resolver={resolver} />
{
prismicH.isFilled.link(data.relatedProject) ? (
<RelatedProjectCta {...relatedProject}/>
)
: null
}
</Layout>
);
};
// Fetch content from prismic - previews but doesn't hot reload
export const getStaticProps = async ({ params, previewData }) => {
const client = createClient({ previewData });
// Default Layout components reused across the site
// If a singleton document is missing, `getStaticProps` will throw a PrismicError.
const seo = await client.getSingle("defaultSeo");
const header = await client.getSingle("header");
const footer = await client.getSingle("footer");
const socials = await client.getSingle("socials");
const projectDetail = await client.getByUID("project", params.uid, {'fetchLinks': 'project.theme, project.client, project.projectTitle, project.projectIntroduction, project.featuredImage'} );
return {
props: {
layout: {
seo,
header,
footer,
socials,
},
...projectDetail
}
};
};
export const getStaticPaths = async () => {
const client = createClient();
const projectDetail = await client.getAllByType("project");
return {
paths: projectDetail.map((page) => prismicH.asLink(page, linkResolver)),
fallback: false,
};
};
export default ProjectDetail;
This is the code of the component that is the link for the related project:
import React from 'react';
import { PrismicText } from '#prismicio/react';
import { PrismicNextImage } from '#prismicio/next';
import { Link } from "../Link";
const RelatedProjectCta = ({ data }) => {
const relatedProject = {
uid: data.relatedProject.uid,
url: data.relatedProject.url,
theme: data.relatedProject.theme,
client: data.relatedProject.data.client,
title: data.relatedProject.data.projectTitle,
introduction: data.relatedProject.data.projectIntroduction,
image: data.relatedProject.data.featuredImage,
}
return (
<section className={`component cta-slice ${relatedProject.theme}`} data-header={relatedProject.theme === "light" && (`is-dark`) || relatedProject.theme === "dark" && ('is-light')}>
<div className="container">
<div className="cta-slice_text-wrapper">
<div className="eyebrow-heading">
Related project
</div>
<h2 className="primary-heading">
<PrismicText field={relatedProject.client}/>
</h2>
<div className="description lead-body">
<PrismicText field={relatedProject.title}/>
</div>
<Link
href={`/work/${relatedProject.uid}`}
className="btn animated-button">
View project
</Link>
</div>
</div>
<div className="cta-slice_background">
<div className="cta-slice_background_image">
<PrismicNextImage
className="block width-100% object-cover"
field={relatedProject.image}
imgixParams={{ q: 80 }}
layout="fill"
/>
</div>
</div>
</section>
)
};
export default RelatedProjectCta
Link component:
import NextLink from "next/link";
import { asLink } from "#prismicio/helpers";
import { linkResolver } from "../utils/linkResolver";
export const Link = ({
href: link,
target,
disabled,
children,
className,
...rest
}) => {
if (disabled) {
return <span {...rest}>{children}</span>;
}
//Standard link
if (typeof link === "string") {
if (link[0] === "/") {
return (
<NextLink href={link}>
<a className={className} {...rest}>
{children}
</a>
</NextLink>
);
}
return (
<a
href={link}
target={target ?? "_blank"}
className={className}
{...rest}
rel="noopener noreferrer"
>
{children}
</a>
);
}
//Unknown link
if (link.link_type === "Any") return null;
//Prismic Link
if (link.link_type === "Web") {
if (!link.url) return null;
//Same page anchor links
if (link.url.includes("://#")) {
const anchor = link.url.split("://")[1];
return (
<a href={anchor} className={className} {...rest}>
{children}
</a>
);
}
return (
<a
href={asLink(link, linkResolver)}
target={target ?? "_blank"}
className={className}
{...rest}
rel="noopener noreferrer"
>
{children}
</a>
);
}
if (link.link_type === "Document") {
return (
<NextLink href={asLink(link, linkResolver)}>
<a className={className} {...rest}>
{children}
</a>
</NextLink>
);
}
if (link.link_type === "Image") {
return (
<a
href={asLink(link, linkResolver)}
className={className}
{...rest}
rel="noopener noreferrer"
>
{children}
</a>
);
}
return null;
};
Summary / Issue
I've created an anime database app using Nextjs with deployment on Vercel. The build was fine and the initial page rendered, but only a few of my dynamic routes are being rendered, the rest display a 404 page. I went into the deploy log and found that for each dynamic page, only 10 routes were being built for every dynmaic route.
Deploy Screenshot from Vercel
While working in development (localhost:3000), there were no issues and everything ran fine.
The routes are based on the id of each title and there are thousands of titles.
My Code
Here is my code for one of the pages using getStaticPaths and getStaticProps
export const getStaticProps = async ({ params }) => {
const [anime, animeCharacters, categories, streaming, reviews] = await Promise.all([
fetch(`https://kitsu.io/api/edge/anime/${params.id}`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/characters`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/categories`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/streaming-links`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/reviews`),
])
.then((responses) =>
Promise.all(responses.map((response) => response.json()))
)
.catch((e) => console.log(e, "There was an error retrieving the data"))
return { props: { anime, animeCharacters, categories, streaming, reviews } }
}
export const getStaticPaths = async () => {
const res = await fetch("https://kitsu.io/api/edge/anime")
const anime = await res.json()
const paths = anime.data.map((show) => ({
params: { id: show.id },
}))
return { paths, fallback: false }
}
[id] is my dynamic route and as you can see, it's only being populated with 10 routes (the first 3 and 7 additional).
Despite the number of shows, I'm looping over each show and grabbing its ID and then passing that as the path.
What I've thought of
The API I'm using is the Kitsu API.
Within the docs, it states: "Resources are paginated in groups of 10 by default and can be increased to a maximum of 20". I figured this might be why 10 paths are being generated, but if that was the case, then why would it work fine in production and in deployment? Also, when I click on each poster image, it should bring me to that specific title by its id, whihc is dynamic, so it shouldn't matter how many recourses are being generated initially.
Code for dynamic page `/anime/[id]
import { useState } from "react"
import { useRouter } from 'next/router'
import fetch from "isomorphic-unfetch"
import formatedDates from "./../../helpers/formatDates"
import Navbar from "../../components/Navbar"
import TrailerVideo from "../../components/TrailerVideo"
import Characters from "./../../components/Characters"
import Categories from "../../components/Categories"
import Streamers from "../../components/Streamers"
import Reviews from "../../components/Reviews"
const Post = ({ anime, animeCharacters, categories, streaming, reviews}) => {
const [readMore, setReadMore] = useState(false)
const handleReadMore = () => setReadMore((prevState) => !prevState)
let {
titles: { en, ja_jp },
synopsis,
startDate,
endDate,
ageRating,
ageRatingGuide,
averageRating,
episodeCount,
posterImage: { small },
coverImage,
youtubeVideoId,
} = anime.data.attributes
const defaultImg = "/cover-img-default.jpg"
const synopsisSubString = () =>
!readMore ? synopsis.substring(0, 240) : synopsis.substring(0, 2000)
const router = useRouter()
if(router.isFallback) return <div>loading...</div>
return (
<div className='relative'>
<div className='z-0'>
<img
className='absolute mb-4 h-12 min-h-230 w-full object-cover opacity-50'
src={!coverImage ? defaultImg : coverImage.large}
/>
</div>
<div className='relative container z-50'>
<Navbar />
<div className='mt-16 flex flex-wrap md:flex-no-wrap'>
{/* Main */}
<div className='md:max-w-284'>
<img className='z-50 mb-6' src={small} />
<div className='xl:text-lg pb-6'>
<h1 className='mb-2'>Anime Details</h1>
<ul>
<li>
<span className='font-bold'>Japanese Title:</span> {ja_jp}
</li>
<li>
<span className='font-bold'>Aired:</span>{" "}
{formatedDates(startDate, endDate)}
</li>
<li>
<span className='font-bold'>Rating:</span> {ageRating} /{" "}
{ageRatingGuide}
</li>
<li>
<span className='font-bold'>Episodes:</span> {episodeCount}
</li>
</ul>
</div>
<Streamers streaming={streaming} />
</div>
{/* Info Section */}
<div className='flex flex-wrap lg:flex-no-wrap md:flex-1 '>
<div className='mt-6 md:mt-40 md:ml-6 lg:mr-10'>
<h1 className='sm:text-3xl pb-1'>{en}</h1>
<h2 className='sm:text-xl lg:text-2xl pb-4 text-yellow-600'>
{averageRating}{" "}
<span className='text-white text-base lg:text-lg'>
Community Rating
</span>
</h2>
<div>
<p className='max-w-2xl pb-3 overflow-hidden xl:text-lg'>
{synopsisSubString()}
<span className={!readMore ? "inline" : "hidden"}>...</span>
</p>
<button
className='text-teal-500 hover:text-teal-900 transition ease-in-out duration-500 focus:outline-none focus:shadow-outline'
onClick={handleReadMore}
>
{!readMore ? "Read More" : "Read Less"}
</button>
</div>
<Categories categories={categories} />
<Reviews reviews={reviews}/>
</div>
{/* Sidebar */}
<section className='lg:max-w-sm mt-10 md:ml-6 lg:ml-0'>
<TrailerVideo youtubeVideoId={youtubeVideoId} />
<Characters animeCharacters={animeCharacters} />
</section>
</div>
</div>
</div>
</div>
)
}
export const getStaticProps = async ({ params }) => {
const [anime, animeCharacters, categories, streaming, reviews] = await Promise.all([
fetch(`https://kitsu.io/api/edge/anime/${params.id}`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/characters`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/categories`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/streaming-links`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/reviews`),
])
.then((responses) =>
Promise.all(responses.map((response) => response.json()))
)
.catch((e) => console.log(e, "There was an error retrieving the data"))
return { props: { anime, animeCharacters, categories, streaming, reviews } }
}
export const getStaticPaths = async () => {
const res = await fetch("https://kitsu.io/api/edge/anime")
const anime = await res.json()
const paths = anime.data.map((show) => ({
params: { id: show.id },
}))
return { paths, fallback: true }
}
export default Post
Screenshot of Errror
Repo
If the API you are working with serves resources in groups of 10, then when you call the API in getStaticPaths you only have 10 id ahead of time. Using static generation in nextjs builds static pages for all the available ids ahead of time in production mode. But when in development mode your server will recreate each page on per request basis. So to solve this problem you can build the first 10 pages and make the rest of the pages to be fallback. Here is how you do it.
export const getStaticPaths = async () => {
const res = await fetch("https://kitsu.io/api/edge/anime")
const anime = await res.json()
// you can make a series of calls to the API requesting
// the next page to get the desired amount of data (100 or 1000)
// how many ever static pages you want to build ahead of time
const paths = anime.data.map((show) => ({
params: { id: show.id },
}))
// this will generate 10(resource limit if you make 1 call because your API returns only 10 resources)
// pages ahead of time and rest of the pages will be fallback
return { paths, fallback: true }
}
Keep in mind when using {fallback: true} in getStaticPaths you need have some sort of loading indicator because the page will be statically generated when you make a request for the first time which will take some time(usually very fast).
In your page that you want to statically generate
function MyPage = (props) {
const router = useRouter()
if (router.isFallback) {
// your loading indicator
return <div>loading...</div>
}
return (
// the normal logic for your page
)
}
P.S. I forgot to mention how to handle errors where API responds with 404 or 500 and the resource doesn't exist to send as props when using fallback in static generation.
So here's how to do it.
const getStaticProps = async () => {
// your data fetching logic
// if fail
return {
props: {data: null, error: true, statusCode: 'the-status-code-you-want-to-send-(500 or 404)'}
}
// if success
return {
props: {data: 'my-fetched-data', error: false}
}
}
// in the page component
import ErrorPage from 'next/error';
function MyStaticPage(props) {
if (props.error) {
return <ErrorPage statusCode={404}/>
}
// else you normal page logic
}
Let me know if it helped or you encountered some error while implementing.
Here is where you can learn more https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation
I'm trying to learn nextjs. Struggling to work out routing with getServerSideProps.
Using a free API I have a list of countries displayed on the DOM. I want to dynamically link to a country and data be fetched and displayed for that specific country.
Heres my code so far
const Country = props => (
<Layout>
<h1>{props.country.name}</h1>
<span>{props.country.capital}</span>
</Layout>
);
export async function getServerSideProps(context) {
const { id } = context.query;
const res = await fetch(`https://restcountries.eu/rest/v2/name/${id}`);
const country = await res.json();
console.log(`Fetched place: ${country.name}`);
return { props: { country } };
}
export default Country;
<div className='container'>
<Head>
<title>Countries List</title>
<link rel='icon' href='/favicon.ico' />
</Head>
<Layout>
<main>
<h1>
Countries{' '}
<span role='img' aria-label='world emoji'>
🌎
</span>
</h1>
<ul>
{countries.map(country => (
<li key={country.name}>
<Link href='/p/[id]' as={`/p/${country.name}`}>
<a>{country.name}</a>
</Link>
</li>
))}
</ul>
</main>
</Layout>
</div>
);
export async function getServerSideProps() {
// Call an external API endpoint to get posts.
const res = await fetch('https://restcountries.eu/rest/v2/all');
const countries = await res.json();
// By returning { props: posts }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
countries,
},
};
}
export default Home;
The URL dynamically routes ok. For example, when you click on Afghanistan the URL shows http://localhost:3000/p/Afghanistan.
My country component however doesn't display anything and undefined is printed to the terminal.
Example of URL and response from URL: https://restcountries.eu/rest/v2/name/Afghanistan
{
name: "Afghanistan"
}
Apologies if a noob question. Trying to learn nextjs
export async function getServerSideProps(context) {
const { id } = context.query;
const res = await fetch(`https://restcountries.eu/rest/v2/name/${id}`);
const country = await res.json();
console.log(`Fetched place: ${country.name}`);
return { props: { country } };
}
you are returning a nested object from above function
{ props: { country:country } }
so this prop will be attached to props as like this:
`props.props`
this is how you should implement
const Country = props => (
<Layout>
<h1>{props.props.country.name}</h1>
<span>{props.props.country.capital}</span>
</Layout>
);
UPDATE
In early version of next.js I think updated after version 9, we were not returning from serverside function by using props. As of now correct way of implementation is
return {
props: {
countries,
},
};
Next.js 13 Update
In next.js 13, if you set app directory, components in this directory will be server-rendered components by default. That means everything will be run on the server and we do not need to write specifiacallygetServerSideProps. in "app" directory, if your file name is surrounded by [..id], it means it is a dynamic route. In page.jsx, you can access id like this
export default function ProductPage({ params }) {
return (
<div>
<h1>Product ID: {params.id}</h1>
</div>
);
}
There's nothing wrong in how you're handling the dynamic routing of the page. The issue is that the data returned by the API is an array but your code expects it to be an object. You can retrieve the first item from the array and pass that to the component from getServerSideProps.
export async function getServerSideProps(context) {
const { id } = context.params; // Use `context.params` to get dynamic params
const res = await fetch(`https://restcountries.com/v2/name/${id}`); // Using `restcountries.com` as `restcountries.eu` is no longer accessible
const countryList = await res.json();
const [country] = countryList; // Get first item in array returned from API
return { props: { country } };
}
const Country = ({ country }) => {
console.log(country);
return (
<>
<h1>{country.name}</h1>
<span>{country.capital}</span>
</>
);
};
export default Country;
Just to add to the accepted answer, you could also destructure to make it (imho) more readable. This is entirely optional though
const Country = ({ country }) => (
<Layout>
<h1>{country.name}</h1>
<span>{country.capital}</span>
</Layout>
);
Evening/Morning All
So, for the 2nd time in as many days, I'm back on here - hat in hand. I've got this kind of condition to work so well elsewhere but here I just can't get the 'loading' div to disappear once the 'pageData' has been fully downloaded. I tried it already with if/else (which I don't normally use in this case). If anyone could cast a 2nd set of eyes over this for me I'd be very thankful.
import DataStore from "flux/stores/DataStore.js";
const ContentData = ({ pageData }) => {
if (!pageData) {
// evaluates to true if pageData is null
return <div>Loading...</div>;
}
return (
<div>
<h2>Homepage template</h2>
<h1>{pageData.title.rendered}</h1>
<div
className="content"
dangerouslySetInnerHTML={{ __html: pageData.content.rendered }}
/>
<div>{pageData.acf.text}</div>
</div> // render content
);
};
class Home extends React.Component {
render() {
let pageData = DataStore.getPageBySlug("home");
return (
<div>
<ContentData />
</div>
);
}
}
export default Home;
Ths data is definitely coming through from the Wordpress API, I can view it all in the console.
Thanks in advance
Terry
Here's the 'getPageBySlug' function
// Returns a Page by provided slug
getPageBySlug(slug){
const pages = this.getState().data.pages;
return pages[Object.keys(pages).find((page, i) => {
return pages[page].slug === slug;
})] || {};
}
}