Nextjs navigating around is too slow, SSR specifically - reactjs

Obviously this question has been asked before but the answers fail to help me.
My problem is server side rendering takes way too long, Navigating from page to page takes like 2.5-4 and sometimes 10 seconds. All I do is 2 queries to the database using prisma and few simple necessary functions.
Im aware that images are a big factor in performance, but despite using a cdn and optimizing images to the maximum it still isn't enough.
My question is how will nextjs handle heavy requests and lots of data in a real big website if it can't handle my pathetic website?.
Please keep in mind that this is my first app using nextjs and im certain that im missing something out.
Here's the link to the site, navigate around and see for yourself, I've added a progress bar in hopes of making it less painful but still the old "smooth react navigation" isn't there.
https://elvito-property.netlify.app/
Link to the repo
https://github.com/VitoMedlej/elvito-property
And ofcourse here is the full code I use to fetch the data using getServerSideProps
`
const isCategoryValid = (categoryQuery : string) => {
let categories = ["apartment", "villa", "comercial", "land", "chalet"]
if (categories.includes(categoryQuery)) {
return categoryQuery
}
return undefined
}`
const isPurposeValid = (purposeQuery : string) => { if (purposeQuery === 'for-sale' || purposeQuery === 'for-rent') { return purposeQuery } return undefined }
`const GetTotalCount = async(type?: string, purpose?: string) => {
const prisma = new PrismaClient()
const totalCount = await prisma
.properties
.count({
where: {
type,
purpose
}
})
return totalCount || 0
}`
`export async function getServerSideProps({query} : any) {
const select = {
id: true,
type: true,
bathrooms: true,
rooms: true,
price: true,
propertySize: true,
images: true,
title: true,
location: true,
purpose: true,
currency: true,
description: true
}
const itemsPerPage = 9
const prisma = new PrismaClient()
const purpose = isPurposeValid(`${query.purpose}`)
const type = isCategoryValid(`${query.category}`)
try {
const currentPage = query.page || 0;
const totalCount = await GetTotalCount(type, purpose) || 0
const totalPages = Math.round(totalCount / itemsPerPage)
let skip = (currentPage * itemsPerPage) || undefined
if (currentPage > totalPages || currentPage < 0)
skip = 0
let data : any = await prisma
.properties
.findMany({
skip,
take: itemsPerPage,
where: {
purpose,
type
},
select
})
// just returning the first image because that's all I need, wish prisma provided
// an easier way to do this but oh well
data.forEach((item : any) => {
item.images
? item.images = item.images[0]
: ''
})
// just a way to deal with bigints
bigInt_To_Number(data)
return {
props: {
results: data,
totalCount
}
}
} catch (err) {
console.log('err 1.4: ', err);
return {props: {}}
} finally {
await prisma.$disconnect()
}
}
`

The only thing that I can suggest is to find a way to switch to get static props or get static paths
Also I hope this discussion can also help you to find the preferred solution for you
https://github.com/vercel/next.js/discussions/32243

Related

How can i get the name of images and use the images

In product page, I want to get all images path that are in a specific folder and send those to client side, so I can use them in client side by passing the paths to Image component of next js. I tried this when I was developing my app via running npm run dev and it was successful. Then I pushed the changes to my GitHub repository and vercel built my app again. Now, when I go to the product page, I get an error from the server. I tried some ways to fix this problem, but I couldn't fix that. For example, I tried changing my entered path in readdir, but the problem didn't fix. Here are my codes:
const getPagePhotosAndReview = async (productName) => {
const root = process.cwd();
let notFound = false;
const allDatas = await fs
.readdir(root + `/public/about-${productName}`, { encoding: "utf8" })
.then((files) => {
const allDatas = { pageImages: [], review: null };
files.forEach((value) => {
const image = value.split(".")[0];
const imageInfos = {
src: `/about-${productName}/${value}`,
alt: productName,
};
if (Number(image)) {
allDatas.pageImages.push(imageInfos);
}
});
return allDatas;
})
.catch((reason) => (notFound = true));
if (notFound) return 404;
await fs
.readFile(root + `/public/about-${productName}/review.txt`, {
encoding: "utf-8",
})
.then((value) => {
allDatas.review = value;
})
.catch((reason) => {
allDatas.review = null;
});
return allDatas;
};
export async function getServerSideProps(context) {
if (context.params.product.length > 3) {
return { notFound: true };
}
if (context.params.product.length < 3) {
const filters = {
kinds: originKinds[context.params.product[0]] || " ",
};
if (context.params.product[1]) filters.brands = context.params.product[1];
const products = getFilteredProducts(filters, true);
if (products.datas.length === 0) {
return {
notFound: true,
};
}
return {
props: {
products: { ...products },
},
};
}
if (context.params.product.length === 3) {
const filters = {
path: context.resolvedUrl,
};
const product = getFilteredProducts(filters, false);
if (product.length === 0) {
return {
notFound: true,
};
}
const splitedPath = product[0].path.split("/");
const pagePhotosAndReview = await getPagePhotosAndReview(
splitedPath[splitedPath.length - 1]
);
if (pagePhotosAndReview === 404) return { notFound: true };
product[0] = {
...product[0],
...pagePhotosAndReview,
};
product[0].addressArray = [
textOfPaths[context.params.product[0]],
textOfPaths[context.params.product[1]],
];
return {
props: {
product: product[0],
},
};
}
}
This is the base code and I tried some ways but couldn't fix the problem. So to fix this problem, I want to ask: how can I get the name of all images in a specific directory and then use those images in client side? And errors that I get: if I go to a page directly and without going to the home of the website, I get internal server error with code of 500 and when I go to a page of my website, and then I go to my product page, I get
Application error: a client-side exception has occurred (see the browser console for more information).
And I should say that I know I should remove public from paths when I want to load an image from public folder. I did it but I still get error.

Saving an ID value from an API to a User with GraphQL

I'm working on a video game website where a user can save a game to a list. How this is supposed to work is when the user clicks "Complete Game", the ID of the game is saved to a state that holds the value. The value is then passed into the mutation, then the mutation runs, saving the ID of the game to the users list of completed games. However, all I'm seeing in the console is this:
"GraphQLError: Variable \"$addGame\" got invalid value { gameId: 740, name: \"Halo: Combat Evolved\",
The above error continues, listing the entirety of the API response, instead of just the gameId.
I was able to successfully add the game to the list in the explorer with the following mutation:
mutation completeGame($addGame: AddNewGame!) {
completeGame(addGame: $addGame) {
_id
completedGameCount
completedGames {
gameId
}
}
}
with the following variable:
{
"addGame": {"gameId": 740}
}
How can I trim down what is being passed into the mutation to just be the gameId?
Below is the entirety of the page, except the return statement at the bottom.
const [selectedGame, setSelectedGame] = useState([]);
const [savedGameIds, setSavedGameIds] = useState(getSavedGameIds());
const [completeGame, { error }] = useMutation(COMPLETE_GAME);
const { id: gameId } = useParams();
useEffect(() => {
return () => saveGameIds(savedGameIds);
});
useEffect(() => {
async function getGameId(gameId) {
const response = await getSpecificGame(gameId);
if (!response.ok) {
throw new Error('Something went wrong...');
}
const result = await response.json();
const gameData = result.map((game) => ({
gameId: game.id,
name: game.name,
cover: game.cover,
summary: game.summary,
platforms: game.platforms,
platformId: game.platforms,
genres: game.genres,
genreId: game.genres,
}));
setSelectedGame(gameData);
}
getGameId(gameId);
}, [])
const handleCompleteGame = async (gameId) => {
const gameToComplete = selectedGame.find((game) => game.gameId === gameId);
const token = Auth.loggedIn() ? Auth.getToken() : null;
if (!token) {
return false;
}
try {
const { data } = await completeGame({
variables: { addGame: { ...gameToComplete } },
});
console.log(data);
setSavedGameIds([...savedGameIds, gameToComplete]);
} catch (err) {
console.error(err);
}
};
With the mutation working in the explorer when I'm able to explicitly define the variable, I am led to believe that the issue is not with the resolver or the typedef, so I'm going to omit those from this post because I don't want it to get too long.
However, I'd be happy to attach any extra code (resolver, typeDef, getSavedGameIds function, etc) if it would allow anyone to assist. The issue (I think) lies in getting my response to match the syntax I used in the explorer, which means trimming down everything except the gameId.
I specifically am extremely suspicious of this line
const gameToComplete = selectedGame.find((game) => game.gameId === gameId)
but I have fiddled around with that for awhile to no avail.
Thank you to anyone who is able to help!
It sounds like you're trying to pass more into your mutation then your schema is defined to allow. In this part:
const { data } = await completeGame({
variables: { addGame: { ...gameToComplete } },
});
You're spreading gameToComplete here which means everything in the gameToComplete object is going to be sent as a variable. If your schema is setup to just expect gameId to be passed in, but your error message is showing that name is also being passed in, you just need to adjust your variables to exclude everything you can't accept. Try:
const { data } = await completeGame({
variables: { addGame: { gameId } },
});

useInfiniteQuery customization

I have to customize useInfiniteQuery from reactQuery in order to get data from all the pages.
https://codesandbox.io/s/cocky-http-zjpff?file=/pages/index.js:961-1025
This is the sandbox implementation available in the documentation in case you need further context on it.
My Api response looks like this.
{
list:{
data1: {...data},
data2: {...data},
...
}
totalPages: 3,
totalCount: 15,
limit: 5
}
Here data1, data2 are just examples of data returning in the list and backend is returning the data in pages, every page is unique.
What I have written yet.
export const useGetAllContactUsRequest = (
limit = 10,
unread = false
): UseInfiniteQueryResult<GetAllContactUsRequestResponse, AxiosError> => {
let page = 1;
return useInfiniteQuery(
["getAllContactUsRequest", unread],
({ pageParam = page }) => getAllContactUsRequest(pageParam, limit, unread),
{
refetchOnWindowFocus: false,
getNextPageParam: (lastPage) => {
console.log(page);
page++;
return lastPage.totalPages >= page ? page : undefined;
},
}
);
};
Essentially what I want is to increment the page and get data until the last page available that is page = 3 in my case.
This is how I am consuming it.
const {
data: readList,
isSuccess: readListSuccess,
hasNextPage: readListHasNextPage,
fetchNextPage: readListFetchNextPage,
} = useGetAllContactUsRequest();
console.log(readListHasNextPage);
This is a function only triggered when the user scrolls at the bottom of the page.
const onScrollLoadMore = () => {
if (readListHasNextPage) {
readListFetchNextPage();
}
}
The current behavior is that it randomly consoles the page numbers from 1 to 4 and hasNextPage criteria always return true.
I need help, it will be appreciated greatly! I have followed up on the documentation and it is confusing.

useEffect and redux a cache trial not working

I have a rather complex setup and am new to React with Hooks and Redux.
My setup:
I have a component, which when first mounted, should fetch data. Later this data should be updated at a given interval but not too often.
I added useRef to avoid a cascade of calls when one store changes. Without useEffect is called for every possible change of the stores linked in its array.
The data is a list and rather complex to fetch, as I first have to map a name to an ID and
then fetch its value.
To avoid doing this over and over again for a given "cache time" I tried to implement a cache using redux.
The whole thing is wrapped inside a useEffect function.
I use different "stores" and "reducer" for different pieces.
Problem:
The cache is written but during the useEffect cycle, changes are not readable. Even processing the same ISIN once again the cache returns no HIT as it is empty.
Really complex implementation. It dawns on me, something is really messed up in my setup.
Research so far:
I know there are libs for caching redux, I do want to understand the system before using one.
Thunk and Saga seem to be something but - same as above plus - I do not get the concept and would love to have fewer dependencies.
Any help would be appreciated!
Component - useEffect
const calculateRef = useRef(true);
useEffect(() => {
if (calculateRef.current) {
calculateRef.current = false;
const fetchData = async (
dispatch: AppDispatch,
stocks: IStockArray,
transactions: ITransactionArray,
cache: ICache
): Promise<IDashboard> => {
const dashboardStock = aggregate(stocks, transactions);
// Fetch notation
const stockNotation = await Promise.all(
dashboardStock.stocks.map(async (stock) => {
const notationId = await getXETRANotation(
stock.isin,
cache,
dispatch
);
return {
isin: stock.isin,
notationId,
};
})
);
// Fetch quote
const stockQuote = await Promise.all(
stockNotation.map(async (stock) => {
const price = await getQuote(stock.notationId, cache, dispatch);
return {
isin: stock.isin,
notationId: stock.notationId,
price,
};
})
);
for (const s of dashboardStock.stocks) {
for (const q of stockQuote) {
if (s.isin === q.isin) {
s.notationId = q.notationId;
s.price = q.price;
// Calculate current price for stock
if (s.quantity !== undefined && s.price !== undefined) {
dashboardStock.totalCurrent += s.quantity * s.price;
}
}
}
}
dispatch({
type: DASHBOARD_PUT,
payload: dashboardStock,
});
return dashboardStock;
};
fetchData(dispatch, stocks, transactions, cache);
}
}, [dispatch, stocks, transactions, cache]);
Action - async fetch action with cache:
export const getXETRANotation = async (
isin: string,
cache: ICache,
dispatch: AppDispatch
): Promise<number> => {
Logger.debug(`getXETRANotation: ${isin}`);
if (CACHE_ENABLE) {
const cacheTimeExceeding = new Date().getTime() + CACHE_NOTATION;
if (
isin in cache.notation &&
cache.notation[isin].created < cacheTimeExceeding
) {
Logger.debug(
`getXETRANotation - CACHE HIT: ${isin} (${cache.notation[isin].created} < ${cacheTimeExceeding} current)`
);
return cache.notation[isin].notationId;
}
}
// FETCH FANCY AXIOS RESULT
const axiosRESULT = ...
if (CACHE_ENABLE) {
Logger.debug(`getXETRANotation - CACHE STORE: ${isin}: ${axiosRESULT}`);
dispatch({
type: CACHE_NOTATION_PUT,
payload: {
isin,
notationId: axiosRESULT,
created: new Date().getTime(),
},
});
}
//FANCY AXIOS RESULT
return axiosRESULT;
}

Storing images' URLs from firebase storage into firebase database - URLs are getting lost in cyberspace

It's a React app with Redux. A form collects a new recipe data from user. The data may include an array of file objects (images) selected by user for upload. I call it imagesToAdd. An action is called startAddRecipe. The following needs to happen:
Write new recipe to firebase database - this returns a ref.key needed for storing images in storage. - this works ok
If there are imagesToAdd a function uploadImages is called that uploads images to firebase storage and returns an array with uploaded images URLs. this works ok
Now the recipe created in (1.) above needs to be updated with the URLs obtained in (2.) - this does NOT work. console.logs are showing the URLs alright but they are not appearing in firebase database:
images
imageNames
0: "voteUpSmiley.png"
(no imageUrls)
...nor in the Redux store:
images: {
imageNames: [
'voteUpSmiley.png'
],
imageUrls: []
},
Oddly the redux-logger tool shows the data ok in console:
images:
imageNames: ["voteUpSmiley.png"]
imageUrls: ["https://firebasestorage.googleapis.com/v0/b/mniam-…=media&token=0e9b7991-0314-4f24-a94a-6b24f93baed7"]
The function uploadImages contains asynchronous tasks - firebase storage upload and a call for URLs so I await for the result and am getting correct one but it's not passed in time to subsequent statements because as said before the firebase database and the redux store are not getting the URLs. I've been looking at this for 2 days and seem to be going in circles.
I include the relevant code below for good people caring to have a look at it. Thank you.
export const startAddRecipe = (recipeData = {}) => {
return (dispatch, getState) => {
const {
authorEmail = '',
brief= '',
createdAt = 0,
ingredients = { general: [] },
keyWords = [],
preparation = { general: [] },
publishedAt = 0,
shared = false,
tips = '',
title = '',
votes = {
downs: [],
ups: [],
},
imagesToAdd = [],
} = recipeData
let imageNames = []
imagesToAdd.map(image => {
imageNames.push(image.name)
})
let recipe = {
authorEmail,
brief,
createdAt,
images: {
imageNames,
imageUrls: [],
},
ingredients,
keyWords,
preparation,
publishedAt,
shared,
tips,
title,
votes,
}
console.log('recipeB4', recipe); //this is showing URLs even before image upload???
database.ref(`recipes/`).push(recipe).then((ref) => {
console.log('ref.key:', ref.key);
if (imagesToAdd.length > 0) {
(async () => {
recipe.images.imageUrls = await uploadImages(ref.key, imagesToAdd)
console.log('recipeAfterImageUpload', recipe); // URLS are shown here but not reflected in the next line
database.ref(`recipes/${ref.key}`).update(recipe).then(() => {
console.log('RECIPE ADDED & UPDATED');
})
})()
}
dispatch(addRecipe({
id: ref.key,
...recipe,
}))
dispatch(startSetRecipeKeyWords())
})
}
}
const uploadImages = (id, imagesToAdd) => {
let imageUrls = []
imagesToAdd.map(image => {
const uploadTask = storage.ref(`/recipePics/${id}/${image.name}`).put(image)
uploadTask.on('state_changed',
(snapShot) => {
// console.log(snapShot)
},
(err) => {
// console.log(err)
},
() => {
storage.ref(`recipePics/${id}`).child(image.name).getDownloadURL()
.then(fireBaseUrl => {
console.log('fireBaseUrl', fireBaseUrl)
imageUrls.push(fireBaseUrl)
})
})
})
return imageUrls
}

Resources