I don't understand why when I refresh a page in my nextjs app, I always get back to the index page.
I have an e-commerce SPA with a catalogue page in index.js and and the products are displayed through the dynamic [name].js page. If I navigate through the browser refresh or back button, the routing is a mess. I think I miss something in the good practices of nextjs.
index.js
import Head from "next/head";
import Catalogue from "../../components/Catalogue";
import { getProducts } from "../../utils/api";
const HomePage = ({ products }) => {
return (
<div>
<Head>
<title>Catalogue</title>
<meta
name="description"
content="Classe moyenne éditions publie des livres et multiples d'artistes, émergeants ou reconnus, en France et à l'international."
/>
<meta
name="keywords"
content="Edition d'artiste, Livres, prints, multiples, art books, librairie, concept store, Bookshop, Bookstore"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Head>
<Catalogue products={products} />
</div>
);
};
export async function getStaticProps() {
const products = await getProducts();
return { props: { products } };
}
export default HomePage;
[name].js
const ProductPage = ({ product }) => {
const router = useRouter();
if (router.isFallback) {
return <div>Loading products...</div>;
}
return (
<div className="wrapper">
<Head>
<title>
{product.name} {product.author}
</title>
</Head>
<div className="col right" key={product.id}>
<div className="colophon" key={product.id}>
<p>
{product.annee}
<br />
Format : {product.size} <br />
{product.pages} pages
<br />
{product.cover} <br />
Printing : {product.printing}
<br />
Paper : {product.paper}
<br />
{product.copies} exemplaires
<br />
{product.price} € + Shipping
<br />
<br />
</p>
<div className="colophon">
<p style= {{
width: '50%',
textAlign: 'end',
color: '#6223f5' }}>
The website is under maintenance. To order a book, please send us an email at <a href="mailto:hello#cmeditions.fr" style={{ textDecoration: 'underline' }}>Hello</a>
</p>
{product.status === true ? (
<button
className="snipcart-add-item buy-button "
variant="dark"
onMouseEnter={(e) => handleEnter(e)}
onMouseOut={(e) => handleExit(e)}
data-item-id={product.id}
data-item-price={product.price}
data-item-url={router.asPath}
data-item-image={getStrapiMedia(product.grid_pic.url)}
data-item-name={product.name}
data-item-description={product.author}
v-bind="customFields"
>
BUY ME!
</button>
) : (
<div className="text-center mr-10 mb-1">
<div
className="p-2 bg-indigo-800 items-center text-indigo-100 leading-none lg:rounded-full flex lg:inline-flex"
role="alert"
>
<span className="flex rounded-full bg-indigo-500 uppercase px-2 py-1 text-xs font-bold mr-3">
Coming soon...
</span>
<span className="font-semibold mr-2 text-left flex-auto">
This article is not available yet.
</span>
</div>
</div>
)}
</div>
</div>
</div>
</div >
);
};
export default ProductPage;
export async function getStaticProps({ params }) {
const product = await getProduct(params.name);
return { props: { product } };
}
// This function gets called at build time
export async function getStaticPaths() {
// Call an external API endpoint to get products
const products = await getProducts();
// Get the paths we want to pre-render based on posts
const paths = products.map(
(product) => `/books/${product.name}`
);
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false };
}
I read this on the nextjs doc, could it be part of the solution?..
If the page uses an optional catch-all route, supply null, [],
undefined or false to render the root-most route. For example, if you
supply slug: false for pages/[[...slug]], Next.js will statically
generate the page /.
The format of paths on getStaticPaths is wrong.
Here is the documentation.
https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation
export async function getStaticPaths() {
// Call an external API endpoint to get products
const products = await getProducts();
// Get the paths we want to pre-render based on posts
const paths = products.map(
(product) => ({ params: { name: product.name } })
);
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false };
}
UPDATE
I've tried your code on local and CSB, and it seemed to work as expected.
You say it only happens in production environment, where are you deploying it?
There might be a problem in the deployment process, so you might want to contact your service provider.
Also, I'm wondering where you put the pages directory. nextjs requires the pages directory to be in the root or src directory.
https://nextjs.org/docs/advanced-features/src-directory
For Basic NGINX Solving:
location / {
try_files $uri $uri.html $uri/ /index.html;
}
Related
In Next.js project I've implemented authentication with Next-Auth.
In index.js (as the Next-Auth documentation explains) I return a User only if there is a session
export default function Home({characters}) {
const {data: session} = useSession()
return (
<>
<Meta
description="Generated by create next app"
title={!session ? "Home - Login" : `Home - ${session.user.name}`}
/>
{session ? <User session={session} /> : <Guest/>}
</>
)
}
In the Guest component I have a Sign In button, and the onClick event points to the signIn method from "next-auth/react"
function Guest() {
return <Layout className="flex flex-col h-screen">
<div className="font-extrabold mb-4 text-3xl">GUEST USER</div>
<Button onClick={() => signIn()}>Sign In</Button>
</Layout>
}
as soon as I click that button I'm redirected to this pages/auth/signin.js page.
This is the page where I can login through EmailProvider or GoogleProvider
import { getCsrfToken, getProviders, signIn } from "next-auth/react"
import { Meta, Layout, Card, InputGroup, Button } from "../../components/ui";
export default function SignIn({ csrfToken, providers }) {
return (
<Layout>
<Meta title="Login"/>
<Card>
<form method="post" action="/api/auth/signin/email">
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
<InputGroup
type="email"
htmlFor="email"
label="Email"
name="email"
/>
<Button type="submit">Sign in with Email</Button>
</form>
{Object.values(providers).map((provider) => {
if(provider.name === "Email") {
return
}
return (<div className="mt-3" key={provider.name}>
<Button onClick={() => signIn(provider.id)}>
Sign in with {provider.name}
</Button>
</div>)
})}
</Card>
</Layout>
)
}
export async function getServerSideProps(context) {
const csrfToken = await getCsrfToken(context)
const providers = await getProviders()
return {
props: { csrfToken, providers },
}
}
When I'm in this page the url is http://localhost:3000/auth/signin?callbackUrl=http%3A%2F%2Flocalhost%3A3000
I wanna know if it's possible to change that url to a more polite one like http://localhost:3000/login or something like this.
This page is already a custom login page as you can see in my [...nextauth].js
pages: {
signIn: '/auth/signin',
},
any suggestions? Thanks guys!
Yes it should work. Lets say you put this in your [...nextauth].js options
pages: {
signIn: '/login'
}
The signIn button will now redirect to /login.
Then you just have to put your login page under pages: pages/login.js.
This question already has an answer here:
How to add new pages without rebuilding an app with +150k static pages?
(1 answer)
Closed 11 months ago.
I've jus started working and learning Next so I have a lot of confusions,
I was using the useEffect on react and it always updated the UI with the new stuff that was added to the API however, its not working on next.js
SO I have an index file
import Link from "next/link";
import react, {useState, useEffect} from "react";
import { useRouter } from 'next/router';
export async function getStaticProps({ res }) {
try {
const result = await fetch(`https://api.pandascore.co/matches/running??sort=&page=1&per_page=10&&token=#`);
const data = await result.json();
return {
props: { game: data },
revalidate: 10 // 10 seconds
};
} catch (error) {
res.statusCode = 404;
return { props: {} };
}
}
const upcomingGames = ({ game }) => {
return (
<div className="container">
<h2>Live Games - </h2>
<div className="columns is-multiline">
{game.map(q => (
<div className="column is-half" key={q.id}>
<div className="inner">
<div className="inner__box">
<Link href = {'/live/' + q.slug} key={q.slug}>
<a className="h2link" key={q.slug}> {q.name}</a>
</Link></div>
</div>
</div>
))}
</div>
</div>
);
}
export default upcomingGames;
This file is connected to a [slug].js file which displays more details about a game,
Now in production when I deployed the app to vercel I have noticed that when a new game is added to the API it displays in the index.js but when I click on it I'm redirected to a fallback(404) page.
After I redeploy my project this is fixed, however every time a new game is added and rendered I'm unable to access its individual page which I defined in [slug].js
export const getStaticPaths = async () => {
const res = await fetch(`https://api.pandascore.co/matches/running?sort=&page=1&per_page=50&token=#`);
const data = await res.json();
const paths = data.map(o => {
return {
params: { slug: o.slug.toString() }
}
})
return {
paths,
fallback: false
}
}
export const getStaticProps = async (context) => {
const slug = context.params.slug;
const res = await fetch(`https://api.pandascore.co/matches/running?search[slug]=${slug}&token=#`);
const data = await res.json();
console.log(data)
return {
props: {
game: data
}
}
}
export default function live({ game }) {
return (
<div className="container">
<h2> Single Game deets.</h2>
{game.map((g) => (
<div className="container" key={g.id} >
<div className="inner-box" key={g.slug}>
{/** Fetch team and display their corresponding score - A bit of code repition :( */}
<div className="score-board-min columns is-mobile is-multiline">
<div className="column is-full"> {g.opponents.slice(0, -1).map((o) => <span className="team" key={o.id}>{o.opponent.name}</span>)}
{g.results.slice(0, -1).map((res, i) => (
<span className="scores" key={i}>{res.score}</span>
))}</div>
<div className="column">
{g.opponents.slice(-1).map((o) => <span className="team" key={o.id}>{o.opponent.name}</span>)}
{g.results.slice(-1).map((res, i) => (
<span className="scores" key={i}><div>{res.score}</div></span>
))}
</div>
</div>
<br />
<div className="lower-box columns is-multine">
<div className="column is-half">
<div className="dark"><span className="is-pulled-left">League</span> <span className="is-pulled-right">{g.league && g.league.name}</span></div>
<div className="dark"><span className="is-pulled-left">Game:</span> <span className="is-pulled-right"> {g.videogame && g.videogame.name} </span></div>
<div className="dark alt"><span className="is-pulled-left">Tournament</span> <span className="is-pulled-right"> {g.tournament && g.tournament.name} | </span></div>
<div className="dark"><span className="is-pulled-left">Series</span> <span className="is-pulled-right"> {g.serie.full_name} | {g.serie.tier.toUpperCase()} </span></div>
<div className="dark alt"><span className="is-pulled-left">Teams</span> <span className="is-pulled-right"> {g.opponents.map((o) => o.opponent.name).join(" vs ")} </span></div>
</div>
</div>
<br />
</div>
</div>
))}
</div>
)
}
During development (next dev) getStaticPaths gets called on every request, but for production it only gets called the next time you run next build. So when a new game is added to the API, the paths named after ${some_new_game_slug} won't exist until you run next build again, i.e., re-deploy. If this type of data changes frequently, you might have to use getServerSideProps for [slug].js as well (so no static paths) or opt for the client-side data fetching approach.
I have an app works perfectly (with no error) on desktop but when I use **mobile device ** ,I found that all pages can not trigger getInitialProps method at client side only if i navigate through Link component
This My code :
return(
<>
<Head>
<title>
welcome to Anas Behhari | blogging posts for
{StaticFunction.Dateit(new Date())} |
{new Date().getFullYear() - 1 + "-" + new Date().getFullYear()}
</title>
</Head>
<div className="main">
<div className="container">
<div className="row">
<div className="col-lg-10 offset-lg-1 js-post-list-wrap Blogs-con">
<h2 className="h4 section-title">
<span> Latest posts </span>
</h2>
{Blogs.map((blog) => (
<Article blog={blog} key={blog._id} />
))}
{BlogList}
</div>
</div>
</div>
</div>
</>
)
blogs.getInitialProps = async (ctx) => {
try {
const res = await axios.get("http://localhost:3000/api/blogs?offset=0&max=5");
const Blogs = res.data;
return { Blogs };
} catch (error) {
return { error };
}
};
export default blogs;
Well, after 2 days of searching on the internet I figured out that I just need to change a bit inside the getInitialProps function.
The main factor was to get rid of the Axios and replace it with Fetch API
export async function getStaticProps() {
const res = await fetch(`http://localhost:8080/api/blogs}`)
const Blogs = await res.json()
if(!Tags) return {props:{error:true}}
return {
props: {
Blogs,
},
}
}
if this solution didn't work getServerSideProps instead of
getStaticProps
I'm trying to render a blog as a card then open it up as a page , but its proving to be difficult using Gatsby. I did the exact same thing fine with react using React router but it doesn't seem to be working with Gatsby. I know I can use GraphQL but surely I can do the same thing using REST. Im using Contentful btw
I switched to reach router as suggested in another post but that doesnt work.
I kept getting this error when I used react-router-dom:
Invariant failed: You should not use <Link> outside a <Router>
Fetching Blog contents
function Blog() {
const [blogs, setBlogs] = useState([])
const [image, setImage] = useState()
const [selectedBlog, setSelectedBlog] = useState(blogs)
useEffect(() => {
fetch("http://cdn.contentful.com...")
.then(response => response.json())
.then(data =>
setBlogs(data.items)
)
}, [])
console.log(blogs)
return (
<>
<div className="card-flex" >
{selectedBlog !== null ? blogs.map((blog =>
<Card title={blog.fields.title} date={blog.fields.date} introduction={blog.fields.introduction} mainBody1={blog.fields.mainBody1} mainBody2={blog.fields.mainBody2} setSelectedBlog={selectedBlog}
/>
)):
<Article title={blogs.find(d => d.fields.title === selectedBlog)} />
}
</div>
</>
)
}
export default Blog
Blog Card
function Card(props) {
console.log(props)
return (
<div class="container">
<div class="card">
<div class="card-header">
<img style={{backgroundImage: "url('https://i.pinimg.com/564x/7f/bb/97/7fbb9793b574c32f5d28cae0ea5c557f.jpg')"}}/>
</div>
<div class="card-body">
<span class="tag tag-teal">{props.tags}</span>
<h4>{props.title}</h4>
<p style={{fontSize:"17px", paddingTop:"10px"}} >{props.introduction}</p>
<div class="card-user">
<Link
to={{
pathname: '/article',
state: {
title: props.title,
introduction: props.introduction
}
}}
>
<button>read more</button>
</Link>
<div class="user-info">
<h5 >{ props.date}</h5>
</div>
</div>
</div>
</div>
</div>
)
}
export default Card
**Article **
import React from 'react'
import './Article.css'
import { useLocation } from "#reach/router"
function Article(props) {
// useLocation to access the route state from Blog.js
const { state = {} } = useLocation();
console.log(state)
return (
<div className="main">
<h1 className="title">{state.title}</h1>
<p className="intro">{state.introduction}</p>
<p className="main1">{state.mainBody1}</p>
<p className="main2">{state.mainBody2}</p>
</div>
)
}
export default Article
I think you are mixing stuff. Gatsby extends from #reach/router so you don't need to use its notation. Your Link should look like:
<Link
to={`/article`}
state={{
title: props.title,
introduction: props.introduction
}}
>
Assuming your /article page exists under /pages folder.
I have been using react for sometime,I decided to try out nextjs. I am building a dashboard, that have a side navigation, and several pages will use that sidebar. What i do in reactjs is have a nested switch. And define my routes. Something like this:
<Switch>
<Route path="/pageOne">
<PageOne />
</Route>
<Route path="/pageTwo">
<pageTwo />
</Route>
</Switch>
but I have gone through a lot of resources, but non seems to talk about extending a page. Another option I am thinking of is to create a subfolder in the dashboard folder, then I will need to import the sidebar component on all the other pages, but this method breaks the DRY principle.
What is the better way to do this?
EDIT
This is what I am trying to archive. When I click the links in the sidebar, I navigate just the right side of the page, while the sidebar persists across the page.
if you're referring to the capacity to dynamically generate pages by appending a given value to some subpage "X" like the behavior that Switch provides, then you should check out the docs on dynamic router
Everything in Next is highly reusable, as it's extremely unopinionated about many things
If you want to dynamically generate subsubpages or subsubsubpages even off of some given pages subdirectory, then you can use the [dynamic].tsx filename and generate static paths using getStaticPaths followed with getStaticProps OR getServerSideProps (which does not use getStaticPaths). To clarify, Next's architecture, you are executing on both the server and the client in the same file when using a pages file (excluding pages/* and pages/api* routes). The pages/api routes are serverless node environments and can be fetched from the default export on the client of any non-pages/* pages/* file. getStaticProps still executes on the server and therefore contributes 0 to your bundle size. Same goes for getStaticPaths. You only need the latter when using getStaticProps within a dynamic file-type ([dynamic].tsx [...dynamic-catch-all].tsx, [[...dynamic-catch-all-optional]].tsx)
check out my profile for some repos with dynamic routing if you'd like
If I'm being honest I had to refresh myself on what the Switch wrapper achieves in CRApp because it's been so long since I've worked in it. I have been using Next with TSX for over a yr now so feel free to shoot any follow up ?s.
Here's an example from a recent client project which dynamically generates 25+ gallery subpages during build
components/Gallery/gallery.tsx
import Image, { ImageLoaderProps } from 'next/image';
import { Timestamp, Container } from '#/components/UI/index';
import { parseUrl } from '#/lib/helpers';
import Link from 'next/link';
import { Gallery } from '#/types/booksy/media';
export default function GalleryMapped({
images,
images_count,
images_per_page
}: Gallery) {
const GalleryImageLoader = ({
src,
width,
quality
}: ImageLoaderProps) => {
return `${src}?w=${width}&q=${quality || 75}`;
};
return (
<Container className='max-w-7xl mx-auto m-12 container'>
<ul
role='list'
className='grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2 sm:gap-x-6 lg:grid-cols-3 max-w-7xl mx-auto'
>
<span className='sr-only'>{images_per_page}</span>
{images
.map((photo, i) => {
const targetedThumbnail = photo.thumbnails['640,0-hr'].url;
// console.log('target: ', targetedThumbnail);
const thumbnailDecoded = decodeURI(targetedThumbnail);
// console.log('decoded: ', thumbnailDecoded);
const fragmentThumbnailURI = parseUrl(thumbnailDecoded);
// console.log('URI Fragments: ', fragmentThumbnailURI);
const thumbnailReconstructed = `${fragmentThumbnailURI!.baseUrl.concat(
fragmentThumbnailURI!.pathname
)}`;
// console.log('reconstructed: ', thumbnailReconstructed);
return (
<li key={i++} className='relative'>
<div className='group cover block w-full rounded-xl bg-redditBG focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-offset-gray-100 focus-within:ring-indigo-500 overflow-hidden'>
<Link
href='/gallery/[id]'
as={`/gallery/${photo.image_id}`}
passHref
shallow={true}
scroll={true}
>
<a id={`/gallery#${photo.image_id}`}>
<Image
loader={GalleryImageLoader}
src={thumbnailReconstructed ?? photo.image}
width='1280'
height='1294'
alt={`${photo.description ?? photo.category}`}
quality={100}
className='object-cover pointer-events-none group-hover:opacity-75 shadow-cardHover'
/>
</a>
</Link>
<button
type='button'
className='absolute inset-0 focus:outline-none'
>
<span className='sr-only'>
View details for {photo.image_id}
</span>
</button>
</div>
<p className='mt-2 block text-sm font-medium text-olive-300 truncate pointer-events-none'>
<Timestamp timestamp={photo.created} />
</p>
<p className='block text-sm font-medium text-gray-500 pointer-events-none'>
{`${++i}/${images_count}`}
</p>
</li>
);
})
.reverse()}
</ul>
</Container>
);
}
Then, it's imported into the pages/gallery/[id].tsx where getStaticPaths and getStaticProps are injected with server-fed props of the current incoming data by apollo client and SWR.
pages/Gallery/[id].tsx
import {
GetStaticPropsContext,
GetStaticPropsResult,
GetStaticPathsContext,
InferGetStaticPropsType
} from 'next';
import { initializeApollo, addApolloState } from '#/lib/apollo';
import {
DynamicNavDocument,
DynamicNavQueryVariables,
DynamicNavQuery,
WordpressMenuNodeIdTypeEnum
} from '#/graphql/generated/graphql';
import { AppLayout } from '#/components/Layout';
import { useRouter } from 'next/router';
import { Container, Fallback } from '#/components/UI';
import useSWR from 'swr';
import { fetcherGallery } from '#/lib/swr-fetcher';
import { Gallery } from '#/types/booksy/media';
import { Configuration, Fetcher } from 'swr/dist/types';
import {
getLatestBooksyPhotos,
getBooksyPhotoById
} from '#/lib/booksy';
import Image, { ImageLoaderProps } from 'next/image';
import { parseUrl } from '#/lib/helpers';
export async function getStaticPaths({
locales
}: GetStaticPathsContext): Promise<{
paths: string[];
fallback: false;
}> {
const data: Response = await getLatestBooksyPhotos();
const pathsData: Gallery = await data.json();
return {
paths: locales
? locales.reduce<string[]>((arr, locale) => {
pathsData.images.forEach(imageId => {
arr.push(`/${locale}/gallery/${imageId.image_id}`);
});
return arr;
}, [])
: pathsData?.images.map(
imageId => `/gallery/${imageId.image_id}`
),
fallback: false
};
}
export async function getStaticProps<P>(
ctx: GetStaticPropsContext
): Promise<
GetStaticPropsResult<
P & {
Header: DynamicNavQuery['Header'];
Footer: DynamicNavQuery['Footer'];
initDataImage: Partial<
Configuration<Gallery, any, Fetcher<Gallery>>
>;
id: string;
}
>
> {
const apolloClient = initializeApollo({
headers: ctx.params ?? {}
});
await apolloClient.query<
DynamicNavQuery,
DynamicNavQueryVariables
>({
notifyOnNetworkStatusChange: true,
fetchPolicy: 'cache-first',
query: DynamicNavDocument,
variables: {
idHead: 'Header',
idTypeHead: WordpressMenuNodeIdTypeEnum.NAME,
idTypeFoot: WordpressMenuNodeIdTypeEnum.NAME,
idFoot: 'Footer'
}
});
const id = ctx?.params ? (ctx.params.id as string) : '21177790';
const initDataGallery = await getBooksyPhotoById(id);
const initDataImage: Gallery = await initDataGallery.json();
return addApolloState(apolloClient, {
props: {
initDataImage,
id
},
revalidate: 600
});
}
export default function GalleryById<
T extends typeof getStaticProps
>({
Header,
Footer,
initDataImage,
id
}: InferGetStaticPropsType<T>) {
const router = useRouter();
const parsedId = router.query
? (router.query.id as string)
: '';
const { data } = useSWR<Gallery>(
`/api/booksy-image-by-id/?category=biz_photo&image_id=${
id ? id : parsedId
}`,
fetcherGallery,
initDataImage
);
const booksyImageLoader = ({
src,
width,
quality
}: ImageLoaderProps) => {
return `${src}?w=${width}&q=${quality || 75}`;
};
const targetThumbnail = data?.images[0].thumbnails['640,0-hr']
.url
? data.images[0].thumbnails['640,0-hr'].url
: data?.images[0].image
? data.images[0].image
: '';
const thumbnailDecoded = decodeURI(targetThumbnail ?? '');
const fragmentThumbnailURI = parseUrl(thumbnailDecoded ?? '');
const thumbnailReconstructed =
`${fragmentThumbnailURI?.baseUrl.concat(
fragmentThumbnailURI.pathname
)}
` ?? '';
return (
<>
{router.isFallback ? (
<Fallback />
) : (
<AppLayout
Header={Header}
Footer={Footer}
title={`${data?.images[0].image_id ?? parsedId}`}
>
<Container
clean
className='my-16 max-w-5xl mx-auto block rounded-xl shadow-cardHover'
>
{data && data.images ? (
<Image
className='rounded-xl object-cover'
src={
thumbnailReconstructed !== ''
? thumbnailReconstructed
: '/doge-404.jpg'
}
loader={booksyImageLoader}
width='1280'
height='1294'
priority
quality={100}
layout='responsive'
/>
) : (
<Fallback />
)}
</Container>
</AppLayout>
)}
</>
);
}
The directory
and there are 48 paths being generated in production builds at the moment