my problem is when i want to fetch data from data.json nothing display it give me many empty array when i try to console.log
screenshot:
screenshot and path of files
i tried hard to change the import path to back with ../ but nothing display to me im so wonder about this error and this is my first time i work with next js
import Image from 'next/image'
const EventPerCityPage = ({data}) => {
console.log("helloa"+data)
return (
<div>
<h1> event in london</h1>
<div>
{data.map((ev)=>{
<a key={ev.id} href={`/events/${ev.city}/${ev.id}`}>
<Image width={300} height={300} alt={ev.title} src={ev.image} />
<h2>{ev.title}</h2>
<p>{ev.description}</p>
</a>
})}
</div>
<p>hello</p>
</div>
)
}
export default EventPerCityPage;
export async function getStaticPaths (){
const {events_categories} = await import ('/data/data.json')
console.log(events_categories+"this code")
const allPaths = events_categories.map(ev=>{
return{
params: {
cat: ev.id.toString()
}
}
})
console.log(allPaths)
return{
paths: allPaths,
fallback:false
}
}
export async function getStaticProps(context){
const id = context?.params.cat
console.log("all path aaaaaaaa" +id)
const {allEvents} = await import('/data/data.json')
console.log(allEvents+"rrrrrrtttttttt")//
const data = allEvents.filter((ev) => ev.city===id)
console.log(data+"eeeeeeeeeezzzzzzz")
return{
props: {data}
}
}
Related
this is my index code and I want to transfer / send this data to another page
I try this code but it's not working I don't why
import { useRouter } from "next/router";
function index() {
const router = useRouter();
const [inputValue, setInputValue] = useState("");
const handleImgInput = (e) => {
e.preventDefault();
router.push({
pathname: "/createproject/uploadnfts/nftdetails",
query: inputValue,
});
};
return (
<form action="" onSubmit={handleImgInput}>
<input
type="email"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
</form>
);
}
this is the data page code. when this page open, I didn't receive the input value from my index page and also what i input in the index file, it appears to my localhost link like this:
http://localhost:3001/data?demo#gmail.com
import { useRouter } from "next/router";
import React from "react";
function Data() {
const router = useRouter();
const {
query: { inputValue },
} = router;
const props = {
inputValue,
};
console.log(props);
return (
<div>
<p>{inputValue}</p>
</div>
);
}
export default Data;
The query property in the router options requires passing an object to it. The query params are formed from the key-value pairs in the object.
See below example from the Next.js docs:
import { useRouter } from 'next/router'
export default function ReadMore({ post }) {
const router = useRouter()
return (
<button
type="button"
onClick={() => {
router.push({
pathname: '/post/[pid]',
query: { pid: post.id },
})
}}
>
Click here to read more
</button>
)
}
Taken from: https://nextjs.org/docs/api-reference/next/router#with-url-object
In your case, this would be something like:
const handleImgInput = (e) => {
e.preventDefault();
router.push({
pathname: "/createproject/uploadnfts/nftdetails",
query: { value: inputValue },
});
};
When you want to access the query params, make sure to reference them like an object's properties. For the example above, this would be:
const { value } = router.query;
I'm currently trying using utterances, which is a github-based open source for comments.
I'm using utterances in my SSG page. Therefore, I'm using client side rendering for getting the utterances component.
Here is the code.
// blog/[id].tsx
/* eslint-disable react/no-danger */
import axios from 'axios';
import { dateFormat } from '_Utils/Helper';
import MarkdownRenderer from '_Components/MarkdownRenderer';
import Comment from '_Components/Comment';
import styles from './blog.module.scss';
const Article = ({ article }: any) => {
return (
<div className={styles.container}>
<div className={styles.header}>
<p className={styles.tag}>{article.data.attributes.tag.data.attributes.tag}</p>
<h1>{article.data.attributes.title}</h1>
<p className={styles.publishedDate}>Published at {dateFormat(article.data.attributes.publishedAt)}</p>
</div>
<main
>
<MarkdownRenderer markdown={article.data.attributes.content} />
<Comment />
</main>
</div>
);
};
export async function getStaticPaths() {
const articlePaths: any = await axios.get(`${process.env.NEXT_PUBLIC_BASE_URL}/api/articles/?populate[0]=*`);
const paths = articlePaths.data.data.map((path: any) => ({
params: { id: `${path.id}` },
}));
return { paths, fallback: false };
}
export async function getStaticProps(ctx: any) {
const { params } = ctx;
const { id } = params;
const article = await axios.get(
`${process.env.NEXT_PUBLIC_BASE_URL}/api/articles/${id}?populate[1]=tag&populate[0]=thumbnail`
);
return {
props: { article: article.data },
};
}
export default Article;
// Comment
const Comment = () => {
return (
<section
style={{ height: '350px', width: '100%' }}
ref={(elem) => {
if (!elem) {
return;
}
const scriptElem = document.createElement('script');
scriptElem.src = 'https://utteranc.es/client.js';
scriptElem.async = true;
scriptElem.setAttribute('repo', 'usernamechiho/Cobb-dev-blog');
scriptElem.setAttribute('issue-term', 'title');
scriptElem.setAttribute('theme', 'github-light');
scriptElem.setAttribute('label', 'comment');
scriptElem.crossOrigin = 'anonymous';
elem.appendChild(scriptElem);
}}
/>
);
};
export default Comment;
and the result
I was wondering why it happens and tried dynamic import with ssr: false.
However, there was nothing but the same.
Is there anything I can look for to get through this?
I'm currently working on a site that pulls Reddit data from different subreddits to show recipes, you can toggle between 3 different ones. That part works fine! I'm able to pull the list of posts from each subreddit, but now I'm trying to make it so when you click a button, it routes you to post details where it'll show the post and comments. I have to do another API call to get that information.
Somewhere along the way, it's getting messed up and it's showing "xhr.js:210 GET https://www.reddit.com/r/recipes/comments/[object%20Object].json 404:" It's a dynamic route, so there are two different parameters I'm trying to use. I did try and console log the parameter for ID(the one showing up as [object object] and it shows up fine by itself and is not an object from what I can tell.
Please see some of the code below where I think things could be going wrong. I'm guessing it's the API call because when I console log it, it only shows the subreddit as an arg but not sure..
redditAPI.js:
import axios from 'axios';
export default axios.create({
baseURL:"https://www.reddit.com/r/"
})
store.js:
import {configureStore} from '#reduxjs/toolkit';
import postReducer from './posts/postSlice';
export const store = configureStore({
reducer: {
posts: postReducer
}
});
postSlice.js:
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import redditApi from '../../common/api/redditApi';
import { redditDetails } from '../../common/api/redditApi';
export const fetchAsyncPosts = createAsyncThunk('posts/fetchAsyncPosts', async (subreddit) => {
const response = await redditApi.get(subreddit)
return response.data.data.children;
});
export const fetchAsyncPostsDetail = createAsyncThunk('posts/fetchAsyncPostsDetail', async (sub, postID) => {
const response = await redditApi.get(`${sub}/comments/${postID}.json`)
return response.data.data.children;
});
const initialState = {
posts: [],
selectedSubreddit: 'recipes.json',
selectedPost: []
}
const postSlice = createSlice({
name: "posts",
initialState,
reducers: {
addPosts: (state, { payload }) => {
state.posts = payload;
},
addSelectedPost: (state, {payload}) => {
state.selectedPost = payload;
},
setSelectedSubreddit(state, action) {
state.selectedSubreddit = action.payload;
}
},
extraReducers: {
[fetchAsyncPosts.pending] : () => {
console.log("Pending");
},
[fetchAsyncPosts.fulfilled] : (state, {payload}) => {
console.log("Fulfilled");
return {...state, posts: payload};
},
[fetchAsyncPosts.rejected]: () => {
console.log("Rejected");
},
[fetchAsyncPostsDetail.fulfilled] : (state, {payload}) => {
console.log("Fulfilled");
return {...state, selectedPost: payload};
},
},
});
export const {addPosts, setSelectedSubreddit} = postSlice.actions;
export const selectSelectedSubreddit = (state) => state.selectedSubreddit;
export const getAllPosts = (state) => state.posts.posts;
export const getSelectedPost = (state) => state.posts.selectedPost;
export default postSlice.reducer;
App.js
import "./App.scss";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import PageNotFound from "./components/PageNotFound/PageNotFound";
import Header from "./components/Header/Header";
import Home from "./components/Home/Home";
import PostDetails from "./components/PostDetails/PostDetails";
import Footer from "./components/Footer/Footer";
import 'bootstrap/dist/css/bootstrap.min.css';
function App() {
return (
<div className="App">
<Router>
<Header/>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/:sub/comments/:postID" element={<PostDetails />} />
<Route path="*" element={ <PageNotFound />} />
</Routes>
</Router>
</div>
);
}
export default App;
Home.js:
import React, {useEffect} from 'react'
import { useDispatch, useSelector } from 'react-redux';
import PostListing from '../PostListing/PostListing'
import { fetchAsyncPosts } from '../../features-redux/posts/postSlice';
function Home() {
const dispatch = useDispatch();
const subreddit = useSelector((state) => state.posts.selectedSubreddit);
useEffect(() => {
dispatch(fetchAsyncPosts(subreddit));
}, [dispatch, subreddit]);
return (
<div>
<div className="jumbotron jumbotron-fluid">
<div className="container text-center">
<h1 className="display-4">Welcome to Tasteful Reddit</h1>
<p className="lead">Toggle between subreddits above to view their recipes.</p>
</div>
</div>
<PostListing />
</div>
)
}
export default Home
postListing.js:
import React from 'react'
import { useSelector } from 'react-redux'
import { getAllPosts } from '../../features-redux/posts/postSlice'
import PostCard from '../PostCard/PostCard';
function PostListing() {
const posts = useSelector(getAllPosts);
const rendering = () => posts.map((post, key) => {
return <PostCard key={key} data={post.data} />;
});
console.log(posts);
return (
<div className="post-wrapper">
<div className="post-container">
{rendering()}
</div>
</div>
)
}
export default PostListing
postCard.js:
import React from 'react'
import parse from 'html-react-parser';
import {Link} from 'react-router-dom';
function PostCard(props) {
const {data} = props;
/* Function to change escaped HTML to string */
const htmlDecode = (input) => {
var doc = new DOMParser().parseFromString(input, "text/html");
return doc.documentElement.textContent;
}
/* Decode reddit JSON's youtube embed HTML */
const youtubeHtmlString = htmlDecode(data.media_embed.content);
/* This function runs through the reddit data to make sure that there is a
an image for the post. If so, shows image
and if its a reddit hosted video or youtube video it will render the video.
Gallery-style posts & all else shows empty div*/
const mediaRender = () => {
if (data.thumbnail !== 'self' && data.thumbnail !== 'default' && data.is_self !== true && data.is_gallery !== true && data.domain !== 'youtu.be' && data.domain !== 'v.redd.it') {
return <img src = {data.url} alt={data.title} className="card-img-top"/>;
} if ( data.is_video == true) {
return (
<div>
<video controls preload = "none">
<source src={data.media.reddit_video.fallback_url} type="video/mp4"/>
Your browser does not support the video tag.
</video>
</div>
)
} if (data.domain == 'youtu.be') {
return (
<div className="Container">
{parse(youtubeHtmlString)}
</div>
)
} else {
return <div></div>
}
}
/* If only text & no photos, render text info*/
const renderSelf = () => {
if(data.is_self == true) {
return (<p>{data.selftext}</p>)
} else {
return <p></p>
}
}
return (
<div className="card mb-3 mx-auto text-center" style={{width: "70%"}}>
<div className="row g-0">
<div className="col-md-5">
{mediaRender()}
</div>
<div className="col-md-7">
<div className="card-body">
<h5 className="card-title">{parse(data.title)}</h5>
<div className="card-text">{renderSelf()}</div>
<div className="card-text"><small className="text-muted">By {data.author}</small></div>
<Link to={`/${data.subreddit}/comments/${data.id}`}>
<button className="btn btn-primary">Go to post</button>
</Link>
</div>
</div>
</div>
</div>
)
}
export default PostCard
postDetails.js:
import React, { useEffect } from 'react'
import { useParams } from 'react-router-dom';
import {useDispatch, useSelector} from 'react-redux';
import { fetchAsyncPostsDetail, getSelectedPost } from '../../features-redux/posts/postSlice';
function PostDetails() {
let {sub, postID} = useParams();
const dispatch = useDispatch();
const data = useSelector(getSelectedPost);
console.log(postID)
useEffect(() => {
dispatch(fetchAsyncPostsDetail(sub, postID));
console.log(dispatch(fetchAsyncPostsDetail(sub, postID)))
}, [dispatch, postID, sub]);
console.log(data);
return (
<div>
PostDetails
</div>
)
}
export default PostDetails
Any help would be appreciated, because I'm lost!
Thanks!
I figured it out today, for those interested!
I finally got it! I figured I was doing something wrong with react router, and that's why I couldn't get it to work but I didn't want to stop using react router. I tried many different ways to fix it, including putting both arguments in an object, I tried changing it by passing what I needed with a pathname AND a state, but the state didn't work right. think that I was passing the state in an incorrect way somehow. I think I wrote it like state{ permalink: data.permalink} or something. I also tried changing it so it only took one parameter, but for some reason the react router Link only wanted to use the post IDs and would go to my page not found if I tried to use permalink. After researching other ways to pass data using React Router, I landed on using From to grab the data from the previous page the user was on and used useLocation to grab the state.
Here is a snippet of what I changed in Link for in PostCard.js. The data was passed in props from postListing.js, just for reference:
return (
<div className="card mb-3 mx-auto text-center" style={{width: "70%"}}>
<div className="row g-0">
<div className="col-md-5">
{mediaRender()}
</div>
<div className="col-md-7">
<div className="card-body">
<h5 className="card-title">{parse(data.title)}</h5>
<div className="card-text">{renderSelf()}</div>
<div className="card-text"><small className="text-muted">By {data.author}</small></div>
<Link to={`/post/${data.id}`}
state={{ from: data}}>
<button className="btn btn-primary">Go to post</button>
</Link>
</div>
</div>
</div>
</div>
)
}
I also stopped using the postId to try and make the API call, and instead only used it to make the dynamic link to the component. After that, I used From's state to grab the permalink(from previous reddit post data) to make the API call to get the comment information.
Here is a snippet of PostDetails.js where I made the changes:
function PostDetails() {
const location = useLocation();
const {from} = location.state;
const comments = useSelector(getSelectedPost);
const dispatch = useDispatch();
useEffect( () => {
dispatch(getPostComments(from.permalink));
}, [dispatch]);
I also found out I was trying to access the data incorrectly in my API call, as the data was structured differently than I originally thought.
Here is the changed thunk in PostSlice.js:
export const getPostComments = createAsyncThunk('posts/getPostComments', async (permalink) => {
const response = await fetch(`http://www.reddit.com${permalink}.json`);
const json = await response.json();
return json[1].data.children.map((comment) => comment.data)
});
There were multiple things wrong with my react app, but hopefully this helps someone else! :)
The second argument to createAsyncThunk is the payloadCreator.
It's called with two arguments, arg which is a single value, and the thunkAPI object.
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
You are passing the thunkAPI argument as the postID value in the GET request.
export const fetchAsyncPostsDetail = createAsyncThunk(
'posts/fetchAsyncPostsDetail',
async (sub, postID) => {
const response = await redditApi.get(`${sub}/comments/${postID}.json`)
return response.data.data.children;
}
);
If you need to pass multiple values to your thunk then pack them into a single object.
export const fetchAsyncPostsDetail = createAsyncThunk(
'posts/fetchAsyncPostsDetail',
async ({ sub, postID }, thunkAPI) => {
const response = await redditApi.get(`${sub}/comments/${postID}.json`)
return response.data.data.children;
}
);
...
useEffect(() => {
dispatch(fetchAsyncPostsDetail({ sub, postID }));
}, [dispatch, postID, sub]);
I have an edit page that will be rendered with an id parameter and it works fine when application is running but while building the nextjs app I get this error
[Error: ENOENT: no such file or directory, rename 'C:\Users\Ahsan Nisar\Documents\GitHub\customer-portal\frontend.next\export\en\companies\edit[id].html' -> 'C:\Users\Ahsan Nisar\Documents\GitHub\customer-portal\frontend.next\server\pages\en\companies\edit[id].html']
the full error
I am not sure what this error is related to or what mistake am I making in my code that this error is occuring during build time.
Here is the code of my page
import { WithAuthorization } from 'common/roq-hocs';
import { MainLayout } from 'layouts';
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import React, { FunctionComponent } from 'react';
import { CompaniesEditView } from 'views/companies-edit';
const CompanyCreatePage: FunctionComponent = () => {
const { t } = useTranslation('companiesEdit');
return (
<MainLayout title={t('title')}>
<WithAuthorization
permissionKey="companies.update"
failComponent={
<div className="mt-16 text-2xl text-center text-gray-600">
<span>{t('noView')}</span>
</div>
}
>
<CompaniesEditView />
</WithAuthorization>
</MainLayout>
);
};
export const getStaticProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale, ['common', 'companiesEdit'])),
},
});
export const getStaticPaths = () => ({
paths: ['/companies/edit/[id]'],
fallback: true,
});
export default CompanyCreatePage;
I think that the problem might be that you are not returning the expected paths model in getStaticPaths function.
Minimal example of this page:
import { GetStaticPaths, GetStaticProps } from 'next';
import { useRouter } from 'next/router';
const CompanyCreatePage = () => {
const router = useRouter();
const { id } = router.query;
return (
<div>
<h1>Company Create Page Content for id: {id}</h1>
</div>
);
};
export const getStaticPaths: GetStaticPaths = async () => {
// Get all possible 'id' values via API, file, etc.
const ids = ['1', '2', '3', '4', '5']; // Example
const paths = ids.map(id => ({
params: { id },
}));
return { paths, fallback: false };
};
export const getStaticProps: GetStaticProps = async context => {
return { props: {} };
};
export default CompanyCreatePage;
Then, navigating to the page /users/edit/3/ returns the following content
Take into account that the fallback param in getStaticPaths changes the behavior of getStaticProps function. For reference, see the documentation
When I click on a link in my /index.js, it brings me to /about.js page.
However, when I'm passing parameter name through URL (like /about?name=leangchhean) from /index.js to /about.js, I don't know how to get it in the /about.js page.
index.js
import Link from 'next/link';
export default () => (
<div>
Click{' '}
<Link href={{ pathname: 'about', query: { name: 'leangchhean' } }}>
<a>here</a>
</Link>{' '}
to read more
</div>
);
Use router-hook.
You can use the useRouter hook in any component in your application.
https://nextjs.org/docs/api-reference/next/router#userouter
pass Param
import Link from "next/link";
<Link href={{ pathname: '/search', query: { keyword: 'this way' } }}><a>path</a></Link>
Or
import Router from 'next/router'
Router.push({
pathname: '/search',
query: { keyword: 'this way' },
})
In Component
import { useRouter } from 'next/router'
export default () => {
const router = useRouter()
console.log(router.query);
...
}
Using Next.js 9 or above you can get query parameters:
With router:
import { useRouter } from 'next/router'
const Index = () => {
const router = useRouter()
const {id} = router.query
return(<div>{id}</div>)
}
With getInitialProps:
const Index = ({id}) => {
return(<div>{id}</div>)
}
Index.getInitialProps = async ({ query }) => {
const {id} = query
return {id}
}
url prop is deprecated as of Next.js version 6:
https://github.com/zeit/next.js/blob/master/errors/url-deprecated.md
To get the query parameters, use getInitialProps:
For stateless components
import Link from 'next/link'
const About = ({query}) => (
<div>Click <Link href={{ pathname: 'about', query: { name: 'leangchhean' }}}><a>here</a></Link> to read more</div>
)
About.getInitialProps = ({query}) => {
return {query}
}
export default About;
For regular components
class About extends React.Component {
static getInitialProps({query}) {
return {query}
}
render() {
console.log(this.props.query) // The query is available in the props object
return <div>Click <Link href={{ pathname: 'about', query: { name: 'leangchhean' }}}><a>here</a></Link> to read more</div>
}
}
The query object will be like: url.com?a=1&b=2&c=3 becomes: {a:1, b:2, c:3}
For those looking for a solution that works with static exports, try the solution listed here: https://github.com/zeit/next.js/issues/4804#issuecomment-460754433
In a nutshell, router.query works only with SSR applications, but router.asPath still works.
So can either configure the query pre-export in next.config.js with exportPathMap (not dynamic):
return {
'/': { page: '/' },
'/about': { page: '/about', query: { title: 'about-us' } }
}
}
Or use router.asPath and parse the query yourself with a library like query-string:
import { withRouter } from "next/router";
import queryString from "query-string";
export const withPageRouter = Component => {
return withRouter(({ router, ...props }) => {
router.query = queryString.parse(router.asPath.split(/\?/)[1]);
return <Component {...props} router={router} />;
});
};
Get it by using the below code in the about.js page:
// pages/about.js
import Link from 'next/link'
export default ({ url: { query: { name } } }) => (
<p>Welcome to About! { name }</p>
)
I know 2 ways to do this:
A Server-Side way, and a Client-Side way.
Method #1: SSR (Server-Side Rendering):
You should use Query Context for that page.
So use getServerSideProps instead of getStaticProps
import React from "react";
export async function getServerSideProps(context) {
const page = (parseInt(context.query.page) || 1).toString();
// Here we got the "page" query parameter from Context
// Default value is "1"
const res = await fetch(`https://....com/api/products/?page=${page}`);
const products = await res.json();
return {props: {products: products.results}}
// will be passed to the page component as props
}
const Page = (props) =>{
const products = props.products;
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>);
}
export default Page
The reason is that: this data cannot be pre-rendered ahead of user's request, so it must be Server-Side Rendered (SSR) on every request.
Static Pages: Use getStaticProps
Changing Content: use getServerSideProps
And here the content is changing based on query Parameters
Reference: https://nextjs.org/docs/api-reference/data-fetching/get-server-side-props
Method #2: Next Router (Client Side):
import {useState, useEffect} from "react";
import { useRouter } from 'next/router'
const Page = () =>{
const [products, setProducts] = useState([]);
const [page, setPage] =useState((useRouter().query.page || 1).toString());
// getting the page query parameter
// Default value is equal to "1"
useEffect(()=>{
(async()=>{
const res = await fetch(`https://....com/api/products/?page=${page}`);
const products = await res.json();
setProducts(products.results);
// This code will be executed only once at begining of the loading of the page
// It will not be executed again unless you cahnge the page
})()
},[page]);
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
export default Page
Reference: https://nextjs.org/docs/api-reference/next/router
If you need to retrieve a URL query from outside a component:
import router from 'next/router'
console.log(router.query)
import { useRouter } from 'next/router';
function componentName() {
const router = useRouter();
console.log('router obj', router);
}
We can find the query object inside a router using which we can get all query string parameters.
Using {useRouter} from "next/router"; helps but sometimes you won't get the values instead u get the param name itself as value.
This issue happens when u are trying to access query params via de-structuring like:
let { categoryId = "", sellerId = "" } = router.query;
and the solution that worked for me is try to access the value directly from query object:
let categoryId = router.query['categoryId'] || '';
let sellerId = router.query['sellerId'] || '';
Post.getInitialProps = async function(context) {
const data = {}
try{
data.queryParam = queryString.parse(context.req.url.split('?')[1]);
}catch(err){
data.queryParam = queryString.parse(window.location.search);
}
return { data };
};
import { useRouter } from 'next/router'
const Home = () => {
const router = useRouter();
const {param} = router.query
return(<div>{param}</div>)
}
Also you can use getInitialProps, more details refer the below tutorial.
get params from url in nextjs
What worked for me in Nextjs 13 pages in the app directory (SSR)
Pass params and searchParams to the page:
export default function SomePage(params, searchParams) {
console.log(params);
console.log(searchParams);
return <div>Hello, Next.js!</div>;
With some builds there may be a bug that can be solved by adding:
export const dynamic='force-dynamic';
especially when deploying on Vercel.
ref: https://beta.nextjs.org/docs/api-reference/file-conventions/page#searchparams-optional
https://github.com/vercel/next.js/issues/43077