Nextjs page goes to 404 on refresh - next.js

I'm using nextjs and graphql for a shopify POC.
I have a component that shows a list of products with links on them that point to the product page
<Link
as={`${handle}/product/${url}`}
href={`/product?id=${item.id};`}>
<a>{item.title}</a>
</Link>
handle is the collection name so the url in the browser will look like
http://localhost:3000/new-releases/product/Plattan-2-Bluetooth but behind the scenes its really just using a page called products and i'm passing the product id.
Now in product.js (pasted below) i'm getting the query string value of the id and doing another query to get the product. All works fine but then if i hit refresh or copy and paste the url into a new window i get 404.
I know this is something to do with routing but i'm not sure what i need to do to fix this. Thanks
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';
class product extends Component {
static async getInitialProps({query}) {
console.log("query", query)
return query ? { id: query.id.replace(';', '') } : {}
}
render() {
const PRODUCT_FRAGMENT = gql`
fragment ProductPage on Product {
title
description
descriptionHtml
id
images(first: 10, maxWidth: 600) {
edges {
node {
id
altText
originalSrc
}
}
}
}
`;
const PRODUCT_FETCH_QUERY = gql`
query PRODUCT_FETCH_QUERY {
node(id: "${this.props.id}") {
__typename
...ProductPage
}
}
${PRODUCT_FRAGMENT}
`;
return (
<div>
<Query query={PRODUCT_FETCH_QUERY}>
{({ data, error, loading }) => {
console.log("data", data)
if (loading) return <p>Loading...</p>
if (error) return <p>Error: {error.message}</p>
return null}
}
</Query>
</div>
);
}
}
product.propTypes = {
};
export default product;

You can try these in a file called next.config.js in the root of your project
module.exports = {
trailingSlash: true
}
Check this link

This is because when you use the next/link component the href prop has the "real" URL to the page with the query parameter for the item ID set. This means that on the client (the browser), Next.js can load the right page (your product.js page) with the parameter for your data query.
But when you load from the server, either by reloading the page or opening it in a new window, Next.js doesn't know what page to load and in this case I think it will try to find the file ./pages/new-releases/product/Plattan-2-Bluetooth.js, which of course doesn't exist.
If you want to have these kinds of URLs you have to make sure the request gets routed to the right page file (./pages/product.js) on the server as well. You can do this by by creating a custom server. There are a bunch of examples in the Next.js repo including one using Express. This is also covered in the "Learn" section of there website in a tutorial called Server Side Support for Clean URLs
If you decide to use Express, you will end up with something like:
server.get('/:collection/product/:productUrl', (req, res) => {
return app.render(req, res, '/product', { productUrl: req.params.productUrl})
})
This will render the product page, and you'll have productUrl available on the query object in getInitialProps(). Of course now you will need to fetch your data with that instead of the product id.

Related

Next.js custom url name (url re-writing)

I have a project in Next.js where I have 2 different users: admin and employee.
I have set up 2 folders inside pages; /admin and /users. Each folder/users has its own subpages. for example /admin/dashboard and /employee/dashboard etc
I am trying to obfuscate the role, so that instead of displaying the url: "/admin/dashboard", if the user is an admin, I would like the url to display "/dashboard" which actually gets the "admin/dashboard" page.
I have tried next.js.config and that works perfectly when I do a full page refresh on /dashboard
module.exports = {
async rewrites() {
return [
{
source: '/:path*',
destination: '/admin/:path*',
},
]
},
}
However, if I set my link to "/dashboard", it does not work. I have tried middleware, and I can get "/" to redirect to "/admin", but I want the opposite. ie if I go to "/" it will use the "/admin/" page, but cannot work out how to achieve my goal (if it is possible !)
if (request.nextUrl.pathname.startsWith('/dashboard')) {
url.pathname = "/admin/dashboard";
return NextResponse.redirect(url);
}
Any help greatly appreciated
You can use as from router.push and the Link component.
For example:
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Example() {
const router = useRouter();
const handleEmployeeClick = () => {
// will show page from /employee/dashboard but show as /dashboard
router.push('/employee/dashboard', '/dashboard');
};
return (
<>
<button onClick={handleEmployeeClick}>
Go to dashboard (employee only)
</button>
<Link href="/admin/dashboard" as="/dashboard">
Go to dashboard (admin only)
</Link>
</>
);
}

How to use the same slug for different routes in Next.js? [duplicate]

I have quite a lot of routes defined and one of the routes is dedicated to user profiles.
Each user has a public profile accessible from HTTP://example.com/#username.
I have tried creating file pages/#[username].js but it doesn't seem to work.
Is there a way to have this behavior without passing # sign with the username because this would greatly complicate index.js handling homepage and I would like to have that code separated.
You can now do this like so in next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/#:username',
destination: '/users/:username'
}
]
}
}
This will make any link to /#username go to the /users/[username] file, even though the address bar will show /#username.
Then, in your /pages/[username].tsx file:
import { useRouter } from 'next/router'
export default function UserPage() {
const { query = {} } = useRouter()
return <div>User name is {query.username || 'missing'}</div>
}
Next.js does not support this yet.
You should watch this issue.

NextJS dev server response time

My main question is; is there a difference in response time for fetching in localhost vs live/production?
I have a project im building in NextJS, with GraphCMS and I'm using GraphQL/graphql-request to fetch the data. When I first start up localhost and the pages loads, I click a link in the page navigation to go to about page and it literally takes 2 seconds for the data to fetch and the page to change. I'm watching the network tab in Chrome DevTools and the .json file status is (pending) and then switches to 200 once the content is downloaded. Here is a screenshot from DevTools:
When I hover over the waterfall, It says the Waiting for server response is 1.86s and content download is 0.46ms. So is the waiting for server response, something because im on a localhost, or is this something to do with the GraphCMS server were its fetching the data from?
Also you may note that the json file size is only 5.2kB, so its not a large fetch.
To give you a little context on the code, my queries & client are stored in the /lib folder:
// /lib/client.js
import { GraphQLClient } from 'graphql-request'
export const graphcmsClient = () =>
new GraphQLClient(process.env.NEXT_PUBLIC_GRAPHCMS_URL, {
headers: {
authorization: `Bearer ${process.env.GRAPHCMS_TOKEN}`,
},
})
// example of a query in ./lib/queries.js
import { gql } from 'graphql-request'
const blogPageQuery = gql`
fragment BlogPostFields on BlogPost {
id
category
content
coverImage {
id
height
url
width
}
excerpt
published
slug
title
}
`
and here is an example where im using getStaticProps and fetching the query data:
export async function getStaticProps({ params, preview = false }) {
const client = graphcmsClient(preview)
const collectionCards = await getAllCollections()
const { page, navigation } = await client.request(pageQuery, {
slug: params.slug
})
if (!page) {
return {
notFound: true
}
}
const parsedPageData = await parsePageData(page)
return {
props: {
page: parsedPageData,
navigation,
collectionCards,
preview
},
revalidate: 60
}
}

NextJS case insensitive route for SSG pages

I am using NextJS to translate a CSV of data into static pages. Each page is pages/[slug].jsx. I'm calling toLowerCase() on the slug value inside [slug].jsx getStaticPaths() and getStaticProps() functions. The generated pages are lowercase. e.g. /ab101, /ab102, /cc500 all resolve to the pages/[slug].jsx page.
Unfortunately, people might hand type the url and may use caps or mixed case for the slug value, currently resulting in a 404.
QUESTION: How can I make routing case insensitive with respect to the slug value?
UPDATE
When I return fallback: true from getStaticPaths(), my [slug].jsx file is hit even when there is not an exact path match. I can then check isFallback as illustrated by Anish Antony below.
Additionally, the items param that is passed to my page will be undefined when the page wasn't found. The router's pathname value is "/[slug]" and not the value of "slug". However, there is an asPath value which contains useful data, e.g. /mixedCaseSlug?param=value&foo=bar.
When the page renders, I check if it's a fallback. If it is, show a LOADING... message. Next will display that and call getStaticProps() to generate the "missing" page. You'll then re-render with the page data. In the event that getStaticProps couldn't get page data, I push a path that will lead to the built-in 404 page.
export default function Page({ item }) {
const { isFallback, push } = useRouter()
const hasPageData = item && Object.keys(item).length > 0
useEffect(() => {
if (!isFallback && !hasPageData) {
push('/page-not-found/error')
}
}, [isFallback, hasPageData])
const loadingMsg = <div>Loading...</div>
const notFoundMsg = <div>Page not found</div>
return isFallback ? loadingMsg : hasPageData ? <Item item={item} /> : notFoundMsg
}
I needed to update getStaticProps() to lowercase the slug param, as it may now be mixed case, but we want to find our page data. And I needed to allow for the case when there really is no data for the slug.
export async function getStaticProps({ params }) {
const { slug } = params
const item = data.find(o => o.Practice_Code.trim().toLowerCase() === slug.toLowerCase())
return {
props: {
item: item ? item : {}
}
}
}
This all seems very kludgy, so I'm still wondering if there is a better way.
NextJS routes are case sensitive.You can use fallback property in getStaticPaths to catch the routes which aren't in the same case as the one provided by default in getStaticPaths.
Edit: I have updated the answer based on the discussion with Dave.
We can give fallback:true or fallback:"blocking" , if we give fallback:true we we can show a custom component which will be displayed till the time page is loaded.For fallback:"blocking" new paths not returned by getStaticPaths will wait for the HTML to be generated,
When we give fallback:true or "blocking" static page will be generated when the user first access the site and the generated page will be served for further visits.
Sample code
export async function getStaticPaths() {
const idList = await fetchAllIds();
const paths = [
];
idList.forEach((id) => {paths.push(`/posts/${id}`)})
return { paths, fallback: true };
}
What we have note is our code in getStaticProps should be case insensitive to get the data irrespective of the one provided in url.
export async function getStaticProps({ params }) {
const { slug } = params;
try {
/// This fetch api should be able to fetch the data irrespective of the case of the slug
or we should convert it to the required case before passing it as a parameter to API
const data= await fetchSlugDetails(slug);
//Same logic should be performed if you are getting data filtered based on slug from an existing array.
return data? { props: { data} } : { notFound: true };
} catch (error) {
console.error(error);
return { notFound: true };
}
}
Note: You have to handle the notfound case and fallback case if you are using fallback:true. For fallback you can get the value isFallback from next/router while the page is being static generated

getStaticPath and the need for filetype in the URL

In /pages I have [page].js and index.js.
[page].js generate needed Pages by the Value of "CustomPage". It's content comes from an Data-JSON-File.
It work like expected, as long as I start on the Homepage and use links inside of my Webpage.
For example I have 2 Pages for now: /impressum and /datenschutz.
So clicking the link "Impressum" open myDomain.com/impressum (and it work, BUT notice, there is no .html at the end).
BUT, if I refresh the page, or type myDomain.com/impressum directly in the addressbar of the browser, I got an not found error (from nginx-server, not from next!).
Second try
As I need a fully static page and I've added getStaticPath and getStaticProps in the file for testing purposes, so that "real" html-files will be created:
import { useRouter } from 'next/router';
import Index from './index';
import config from '../content/config.yml';
import CustomPage from '../src/components/CustomPage';
const RoutingPage = () => {
const { customPages } = config;
const router = useRouter();
const { page } = router.query;
const findMatches = (requestedPage) =>
customPages.find((customPage) => customPage.name === requestedPage) ||
false;
const customPageData = findMatches(page);
if (customPageData !== false) {
return <CustomPage pageContext={customPageData} />;
}
return page === 'index' ? (
<Index page={page} />
) : (
<p style={{ marginTop: '250px' }}>whats up {page}</p>
);
};
export async function getStaticPaths() {
return {
paths: [
{ params: { page: 'impressum' } },
{ params: { page: 'datenschutz' } },
],
fallback: false, // See the "fallback" section below
};
}
export async function getStaticProps({ params }) {
return { props: { page: params.page } };
}
export default RoutingPage;
This generates the single pages as real html-files:
But this lead me to the next issue:
I've implemented internal Links in the Webpage like this:
which still lead a user to myDomain.com/impressum, now additionally there is myDomain.com/impressum.html available. From SEO perspective, this are two different paths.
How do I get them unified, so that I have only one path - regardles of whether if I open it from within my Webpage, or enter it directly.
Workaround Idea (??)
Sure, I could everywhere use something like:
<Link href={`/${item.page}.html`}>
But this only work if the Page is exported and copied to the Server. For next dev and next start this won't work, because the .html-File don't exist.... and so I'll lost the "page preview" while working at the page.
So only Idea I have is to set an ENV-Variable for .env.development & .env.production and encapsulate the -Component from NEXT in a HOC.
In that HOC I could check if I'm currently in dev or prod and don't use .html for those links... otherwise add the .html to the link.
What YOU say about this. Do you have any other solution?
I don't know if it's state of the art, but as little workaround I did this:
I place the next/link-Component in a HOC and check if it's run on development or production (process.env.NODE_ENV):
import React from 'react';
import Link from 'next/link';
const LinkHoc = (props) => {
const { as, href, children } = props;
if (process.env.NODE_ENV === 'production') {
return (
<Link
{...props}
as={as ? `${as}.html` : ''}
href={href ? `${href}.html` : ''}
/>
);
}
return <Link {...props}>{children}</Link>;
};
export default LinkHoc;
With this workaround you get mydomain.com/impressum links in DEV and mydomain.com/impressum.html in production.
Only thing what to do at least is to rename the JSON-Files for the generated pages.
They are in /out/_next/data/XYZranadomString/.
They are named like impressum.json and you need to rename it to impressum.html.json to fix the 404 error on clientside for this files.
Would love to see a better Solution, so if you have any suggestions, please let me know!

Resources