Nextjs: render page or show 404 page based on user data - javascript

We are currently working on a Nextjs project which includes a number of dynamic pages. We have come to a stage where we are defining the dynamic paths using getStaticProps and getStaticPaths. So far we managed to define 404 pages in dynamic pages with a similar structure to the below:
export async function getStaticProps(context) {
const { id } = context.params
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
const user = await response.json()
return {
props: { user },
notFound: Object.keys(user).length === 0
}
}
export function getStaticPaths() {
return {
paths: [],
fallback: 'blocking'
}
}
Using version 10's notFound value and fallback: blocking allows us to have the page return a 404 status code (for some reason fallback: true returns a 200 status code).
The issue we currently have is to display a 404 page to pages that depend on user data such as username or user locale. One way we thought we could solve this is by having those pages as Server Side generated rather than Static Site generated by using getServerSideProps:
export async function getServerSideProps(context) {
const userId = getCookie('user_id')
const response = await fetch(`___/checkUserAccess/${userId}`)
const access = await response.json()
return {
props: { ... },
notFound: !access.userAllowed
}
}
I suppose I am answering my own question here as going for Server Side for these pages to show 404 pages based on user data seems logical. However the concern is the impact it may have on the website as a whole. These pages could easily end up being hundreds of pages.
Should we be concerned about this? Or maybe there's another way to go about solving this?

Related

404 on new posts, after build-time, using Next.js and Sanity.io

My goal is to be able to add new posts the CMS(Sanity.io) after build-time, and for the site to be able to display received data on a provided slug using dynamic routes.
Everything works well in dev environment, but in production it seems the page can't use the new slugs provided from the CMS, and responds with a 404.
Here's my code getting the posts from the CMS, inside my [slug].tsx file.
[slug.tsx]
export const getStaticPaths: GetStaticPaths = async () => {
const query = `
*[_type=='post']{
_id,
slug {
current
}
}`
const posts = await sanityClient.fetch(query)
const paths = posts.map((post: Post) => ({
params: {
slug: post.slug.current,
},
}))
return {
paths,
fallback: 'blocking',
}
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const query = `
*[_type=='post' && slug.current == $slug][0]{
_id,
publishedAt,
title,
description,
mainImage,
gallery[0]->{
title,
link,
images,
display,
},
slug,
body
}`
const post = await sanityClient.fetch(query, { slug: params?.slug })
return {
props: {
post,
},
revalidate: 10,
}
}
When I add new posts in the CMS I immediately see the thumbnail come up on the website, with the correct image and other data, but when I click the thumbnail I get 404.
I hope someone can help me!
A dirty-quick fix: re-deploy your front end code (your nextjs app). In my case, I just commit a new change which triggers the building and deployment. And you will see your new page data of the slug.
But I believe there must be a solid solution. I thing it has something to do with 'Incremental Static Regeneration' in NextJS. But even I add revalidate: 10 to the getStaticProps(), it doesn't seem to work. Hope someone else could provide some tips on such issue.
You should be using getServerSideProps instead since you want new blog posts in Sanity to show up on your production as soon as it's published and not on every build.

How to show error page without changing current URL

I have a custom 500 error page called 500.tsx and I want to able to show this page when request from client fails with status of 500, but keeping client on old url (for example /auth/register). I have a axios listener on error which should do all the work. But could not find a way to do this using next/router. Any help appreciated
Didn't realize OP wanted to show 500 page at first.
You could look into router.isFallback but I am not entirely sure if that's appropriate for this.
It's supposed to be for Page Loading Indicators, but in your case, since the page will never load, it could work.
https://nextjs.org/docs/basic-features/data-fetching#fallback-true
return router?.isFallback
? <Custom500Page/>
: <RegularPage/>
You can set this variable from getStaticPaths method, which runs during build time and is usually used for [slug] pages.
// This function gets called at build time
export async function getStaticPaths() {
// Fetch paths to your slug pages here
return {
// paths,
fallback: true
}
}
Showing 404 page is quite straight forward & This is how we do.
Rendering 404 page
You can conditionally export a variable called notFound as true from getStaticProps method.
If the URL is invalid and you don't data to render the page, you simply pass that notFound prop as true and then the default or custom 404 page will show up automatically preserving your invalid url.
// This function gets called at build time
export async function getStaticProps({ params, preview = false }) {
let pageData
try {
// fetch pageData from API here
} catch (error) {
// catch errors
}
// check validity or emptyness of data
// invalid URL (slug) -> empty data from API
// valid URL (slug) -> valid data from API
return isObjectEmpty(pageData)
? { notFound: true }
: {
props: {
pageData
// pass other props to the page
}
}
}
NextJS Docs on getStaticProps & not-found
https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation

userRole loads white page after first test unless preserverURL: true is used, preventing navigating directly to a page with .page

When setting up a test the .page method won't visit a page or the page will be white because of a failure with the authentication process with userRole. The only fix so far has been to add preserveURL: true the issue with this is the tests are taking a substantially longer time as the test must then navigate to the proper page.
Every post and docs I've read say this should just work, I'm hoping someone here can point me in the right direction or offer some things I can try.
Adding it all in one file, but each is split into their own file.
// authentication file .js
import { Role } from "testcafe";
const userLogins = require('../logins.json');
let users = [];
let passwords = [];
userLogins.forEach( data => {
users.push(data.name);
passwords.push(data.password);
})
const admin = Role('https://foo.example.com/', async t => {
await t
.typeText('#email', users[0], { paste: true, replace: true })
.typeText('#password', passwords[0], { paste: true, replace: true })
.click('#login-btn');
}); // adding the option { preserveURL: True } here will cause all tests to pass successfully if I add code to each test to nav to the correct page
// and disable .page in the test spec
export { admin };
// page model file .js
import { Selector, t } from "testcafe";
class FooPage {
constructor() {
this.searchInput = Selector('#searchInput');
this.orderCount = Selector('#orderNumber');
this.businessName = Selector('#businessName');
this.contactNumber = Selector('#contactNumber');
};
async searchResults(selector, searchText) {
// enter search term
await t
.typeText(this.searchInput, searchText, {paste: true, replace: true})
.pressKey('enter');
// check how many rows were returned
const rowCount = await this.orderCount.count;
let searchResults = []
// verify all rows returned contain only our search text
for (let i = 0; i < rowCount; i++) {
let text = await selector.nth(i).innerText;
searchResults.push(text);
await t.expect(searchResults[i]).contains(searchText);
}
}
export default FooPage;
// test spec file .js
import { admin } from "../authentication";
import FooPage from "../FooPage";
const fooPage = new FooPage();
fixture `Test searching foo orders`
.page`https://foo.example.com/#/foo_orders` // this works for first test then loads white page thereafter
.beforeEach( async t => {
await t
.resizeWindow(1284, 722)
.useRole(admin)
});
// this test will work correctly
test(`User can search via order number`, async t => {
await fooPage.searchResults(fooPage.orderCount, 'FOO111');
});
// this test will load a white page and fail
test(`User can search via business purchaser`, async t => {
await fooPage.searchResults(fooPage.businessName, 'Foo Company');
});
// this test will load a white page and fail
test(`User can search via phone number`, async t => {
await fooPage.searchResults(fooPage.contactNumber, '555-555-5555');
});
I won't be able to post an example site as it's all proprietary. This will work though if I remove the .page and add in preserveUrl: true into the authentication file. The spec, page model and authentication file all clearly work.
But, I can't use .page to navigate directly to the page I want. This is increasing test times and breaking the cardinal rule of navigating directly to the page to test.
Your code is correct. It looks like your app has some specifics, which do not allow it to work correctly without the preserveUrl option.
However, it's difficult to determine the cause of the issue without a working example. Since you can't share an example here, I would ask you to send it to the official TestCafe support email: support#devexpress.com
Please note that the DevExpress TestCafe policy prevents us (TestCafe Team) from accessing internal resources without prior written approval from a site owner. If we need to access non-public parts of your website or pass through authorization pages, please ask the website owner to send us (support#devexpress.com) a written confirmation. It must permit DevExpress personnel to remotely access the website and its internal resources for research/testing/and debugging purposes.

getServerSideProps is not updating data with import

I'm having a problem with getServerSideProps in NextJS not updating values on page load. It seems to be having the same effect as using getStaticProps when calling from data that is an imported JSON file.
Here's the applicable code:
/update/[...id].js
import { hasPermission, getPermissions } from '../../lib/permissions'
...
//in the page function I use the ternary to check permission of the user
{ (hasPermission(session.user.email, `${params.id[0]}-edit`, permissions)) ? <HasPerm /> : <NoPerm /> }
...
export async function getServerSideProps(context) {
const params = context.query
const permissions = getPermissions()
return {
props: { params, permissions }
}
}
/lib/permissions.js
import permissions from '../db/users.json'
export function hasPermission(username, group, perm = false) {
if (!perm)
perm = permissions
if (username in perm && perm[username].groups.includes(group)) {
return true
}
if (perm.all.groups.includes(group)) {
return true
}
return false
}
export function getPermissions() {
return permissions
}
For SSR pages that were understandably doing this to begin with, I pass in the permissions to the page in getStaticProps and pass those values into my hasPermission() function in those pages so that they are revalidated on permissions change. Yet there is no revalidation option in getServerSideProps so the data is not revalidated at all. I was using getInitialProps before but pretty much everyone discourages that because it disables ASO.
How do I use getServerSideProps such that my imported json is at least revalidated?
I ended up not using the json import and used fs instead for other reasons but the issue is resolved by using dynamic imports in the getServerSideProps:
export async function getServerSideProps(context) {
const permissions = await import('../db/users.json')
}
Also don't make my same mistake of passing the whole permissions database to every user as was being done by using a function like getPermissions in getStaticProps to get the data to the hasPermission function in the page. Instead, only serve data with getServerSideProps if the user has permission. In other words, ensure you're holding the data back from the server-side. Then you can also pass through an individual's permissions to the page if you need to, like for displaying a message saying you don't have permission.

How to call a serverless function in Next.js getStaticPaths [duplicate]

This question already has an answer here:
Fetch error when building Next.js static website in production
(1 answer)
Closed 11 months ago.
I am using Next.js with the Vercel deployment workflow, I am following this guide to try and setup page generation at buildtime. The specific section shows the following example to generate pages based on an external API's response:
// This function gets called at build time
export async function getStaticPaths() {
// Call an external API endpoint to get posts
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: false } means other routes should 404.
return { paths, fallback: false }
}
// 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 } }
}
I want to do this exactly, however I wrote my API as a Node.js serverless function within the same code repository, it is not an external api.
I tried to do the following to call on my api:
// This function gets called at build time
export async function getStaticPaths() {
const res = await fetch('/api/get-designs');
const designs = await res.json();
// Get the paths we want to pre-render based on posts
const paths = designs.map(design => ({
params: { id: design.id },
}))
return {
// Only posts returned by api are generated at build time
paths: paths,
// Enable statically generating additional pages
fallback: true,
}
}
However I get an error that the fetch api url must be absolute. Because of the way Vercel deploys, I won't always have the same deployment URL, so I don't think I can just use a hardcoded value here. Also, I am suspecting that because this function runs at buildtime, that my function is not running yet, therefore can not be called. I am still trying to wrap my head around this Next.js statically generated site workflow, but basically I am confused because they seem to encourage using serverless functions, and this getStaticPaths method for page generation, but they don't seem to work together unless I am missing something.
Is there a way I can run my api to get these results at build time? Any guidance would be much appreciated!
Thanks!
In this case, we can extract the server logic into a function and that function can be used directly inside your api route file. So, for CR we can use /api/whateverRoute and inside getStaticPaths we can use that function itself directly.

Categories