How to deal with slow loading pages in next js - next.js

I have a page which requires making an HTTP request to an API which might take more than 10 seconds to respond, and my host limits me to 10 second executions.
Is there a way that I can load a temporary page or something and then asynchronously load the rest of the data? I'm currently doing this:
export async function getServerSideProps({ params }) {
const res = await fetch(`${process.env.API_USER}name=${params['name']}`)
const videos = await res.json()
const tag_res = await fetch(`${process.env.API_TAG}author=${params['name']}`)
const tags = await tag_res.json()
const name = params['name']
return {
props: { videos, tags, name }, // will be passed to the page component as props
}
}

Lets's move your HTTP request from getServerSideProps to client side (your components)
// Functional component
useEffect(() => {
fetch(...)
}, [])
// Class-based component
componentDidMount() {
fetch(...)
}
If you still want to stick with getServerSideProps, maybe you have to upgrade/switch your host, or implement a proxy/wrapper server for handling your HTTP request and return response as fast as it can

Related

nextjs-vercel api routes not working in production and development [duplicate]

I'm new to Next.js and I'm trying to understand the suggested structure and dealing with data between pages or components.
For instance, inside my page home.js, I fetch an internal API called /api/user.js which returns some user data from MongoDB. I am doing this by using fetch() to call the API route from within getServerSideProps(), which passes various props to the page after some calculations.
From my understanding, this is good for SEO, since props get fetched/modified server-side and the page gets them ready to render. But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps(). So what am I suppose to do to comply to good practice and good SEO?
The reason I'm not doing the required calculations for home.js in the API route itself is that I need more generic data from this API route, as I will use it in other pages as well.
I also have to consider caching, which client-side is very straightforward using SWR to fetch an internal API, but server-side I'm not yet sure how to achieve it.
home.js:
export default function Page({ prop1, prop2, prop3 }) {
// render etc.
}
export async function getServerSideProps(context) {
const session = await getSession(context)
let data = null
var aArray = [], bArray = [], cArray = []
const { db } = await connectToDatabase()
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
if (session) {
const hostname = process.env.NEXT_PUBLIC_SITE_URL
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/user`, options)
const json = await res.json()
if (json.data) { data = json.data }
// do some math with data ...
// connect to MongoDB and do some comparisons, etc.
But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().
You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps. Note that this only applies to calls to internal API routes - it's perfectly fine to call external APIs from getServerSideProps.
From Next.js getServerSideProps documentation:
It can be tempting to reach for an API Route when you want to fetch
data from the server, then call that API route from
getServerSideProps. This is an unnecessary and inefficient approach,
as it will cause an extra request to be made due to both
getServerSideProps and API Routes running on the server.
(...) Instead, directly import the logic used inside your API Route
into getServerSideProps. This could mean calling a CMS, database, or
other API directly from inside getServerSideProps.
(Note that the same applies when using getStaticProps/getStaticPaths methods)
Here's a small refactor example that allows you to have logic from an API route reused in getServerSideProps.
Let's assume you have this simple API route.
// pages/api/user
export default async function handler(req, res) {
// Using a fetch here but could be any async operation to an external source
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
res.status(200).json(jsonData)
}
You can extract the fetching logic to a separate function (can still keep it in api/user if you want), which is still usable in the API route.
// pages/api/user
export async function getData() {
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
return jsonData
}
export default async function handler(req, res) {
const jsonData = await getData()
res.status(200).json(jsonData)
}
But also allows you to re-use the getData function in getServerSideProps.
// pages/home
import { getData } from './api/user'
//...
export async function getServerSideProps(context) {
const jsonData = await getData()
//...
}
You want to use the logic that's in your API route directly in
getServerSideProps, rather than calling your internal API. That's
because getServerSideProps runs on the server just like the API routes
(making a request from the server to the server itself would be
pointless). You can read from the filesystem or access a database
directly from getServerSideProps
As I admit, what you say is correct but problem still exist. Assume you have your backend written and your api's are secured so fetching out logic from a secured and written backend seems to be annoying and wasting time and energy. Another disadvantage is that by fetching out logic from backend you must rewrite your own code to handle errors and authenticate user's and validate user request's that exist in your written backend. I wonder if it's possible to call api's within nextjs without fetching out logic from middlewars? The answer is positive here is my solution:
npm i node-mocks-http
import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
return (
<section>
<h2>Latest News</h2>
<NewsCard news={news} />
</section>
);
}
export async function getServerSideProps() {
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
async function callMids(req, res, index, ...mids) {
index = index || 0;
if (index <= mids.length - 1)
await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
}
await callMids(
req,
res,
null,
dbConnectMid,
logger,
newsController.sendAllNews
);
return {
props: { news: res._getJSONData() },
};
}
important NOTE: don't forget to use await next() instead of next() if you use my code in all of your middlewares or else you get an error.
Another solution: next connect has run method that do something like mycode but personally I had some problems with it; here is its link:
next connet run method to call next api's in serverSideProps
Just try to use useSWR, example below
import useSWR from 'swr'
import React from 'react';
//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());
const Categories = () => {
//getting data and error
const { data, error } = useSWR('/api/category/getCategories', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
if (data){
// {data} is completed, it's ok!
//your code here to make something with {data}
return (
<div>
//something here, example {data.name}
</div>
)
}
}
export default Categories
Please notice, fetch only supports absolute URLs, it's why I don't like to use it.
P.S. According to the docs, you can even use useSWR with SSR.

NextJS - SSR Getting _next/data.json requst

I have a page uses getServerSideProps and when I request this page directly everything works as expected but when I request this page on the client through next router I see request which has this JSON path like https://****/_next/data/uU96tn57Tno0N1e5kERHx/data.json
How can I prevent this request when accessing the page on the client side through next router?
Should I use shallow routeing? And handle the request on useEffects method?
My page looks like
import React from "react";
import axios from "axios";
const sendGetRequest = async () => {
try {
const resp = await axios.get("https://dog.ceo/api/breeds/image/random");
return resp.data.message;
} catch (err) {
console.error(err);
}
};
function Paged(props) {
return (
<div>
<p>{props.dogstring}</p>
</div>
);
}
export async function getServerSideProps(context) {
const dogstring = await sendGetRequest();
return {
props: {
dogstring: dogstring,
},
};
}
export default Paged;
Accessing the page
<Link href={'/paged'}><a>ssr page</a></Link>
I want to prevent the JSON path request because I don't want to be 2 latency to the SSR page when accessing it through next router on the client side

Setting initial state using recoil in a nextjs app with ISR and SWR

I'm trying to figure out how to set my initial recoil state while still using nextjs`s ISR feature.
So I made a product.ts file inside of a states directory the file contains the following code
const productsState = atom({
key: 'productState',
default: []
})
I thought about calling my api here and instead of setting the default as an empty array have it filled with data from the api call, but I'm sure I would lose out of ISR and SWR benefits that nextjs brings?
So I thought about setting initial state inside of the getStaticProps method
export const getStaticProps: GetStaticProps = async () => {
const res: Response = await fetch("http://127.0.0.1:8000/api/products");
const {data} = await res.json();
return {
props: {
data
},
revalidate: 10
}
}
But this would only run once on build time so data would be stale, so I made a hook to get my products using SWR
import useSWR from "swr";
import { baseUrl} from '../lib/fetcher'
export const useGetProducts = (path: boolean | string, options: {} = {}) => {
if (!path) {
throw new Error("Path is required")
}
const url = baseUrl + path
const {data: products, error} = useSWR(url, options)
return {products, error}
}
this is then called inside of the page component
const Home: NextPage = ({data}: InferGetStaticPropsType<typeof getStaticProps>) => {
const {products, error} = useGetProducts('/api/products', {
initialData: data,
})
}
Now I'm just wondering if this is a viable way to set initial state in recoil without sacrificing ISR and SWR benefits?
coming from vue/nuxt I would make a global store where I would call my api and set state to the api data, but it seems in react/recoil it's a bit different?

Page with getInitialProps and getStaticProps

I have a page where I need to fetch data with axios which contains credentials and depending on the parameter in the url make another request using the data from the previous request.
I don't know how I can approach this since I can use both getInitialProps and getStaticProps in pages, and in components I can't use them at all.
The code below works, but I don't know how to break it down so that the login is done on the servar side so that I can still get parameters from the URL.
function Result({surveyId, responseId, sessionKey}) {
return (
<>
<div>surveyId: {surveyId}</div>
<div>responseId: {responseId}</div>
<div>sessionKey: {sessionKey}</div>
</>
)
}
Result.getInitialProps = async ({ query }) => {
// Example of URL
// http://localhost:3000/result?surveyId=12345&responseId=6
const surveyId = query.surveyId
const responseId = query.responseId
const data = { method: 'get_session_key', params: ['admin', 'password'], id: 1 }
const options = {
headers: {
'connection': 'keep-alive',
'content-type': 'application/json'
}
}
const sessionKey = await axios.post(url, data, options).then(
(response) => {
return response.data.result
},
(error) => {
console.log(error)
}
)
return {
surveyId: surveyId,
responseId: responseId,
sessionKey: sessionKey,
}
}
getStaticProps, getInitialProps or getServerSideProps, they all executed only in the pages. Because client makes the request to the pages, so behind the scene, the way how next.js sets up the routing, whenever a request hits a route, next.js first checks if this page has any of those function, and if it has it runs those functions first, gets the results as props and then runs the component functions and passes the props to the component.
getsStaticProps is used for static file generation. A good example is generating of blogs. When you run npm run build,next js will run all api calls and populate the page with blogs. So actually, if you check the build folder, inside html files of pages that getStaticPath is executed, all the data will be already inside that html file. Data will be cached by the server so when user makes a request to those statically generated files, data will be served right away. So you should not run the login process in the getStaticProps since login is a dynamic process.
All those functions are used for prerendering for better SEO optimization. But if you want to load user-specific data you dont need to prerender user-specific data for seo purpose. You could just do client-side as well.
Or you could use next.js api functions, you would be writing the function inside api directory, and from getServerSideProps you would send a request to the api route. That way, if you need to run same code in a different page, instead of writing the same code for authentication, you would be making request the api function and it would handle for you.
I found another solution which uses getStaticPaths and dynamic routes. In my case /pages/survey/[...params].js.
export default function Result({ surveyId, responseId, sessionKey }) {
return (
<>
<div>surveyId: {surveyId}</div>
<div>responseId: {responseId}</div>
<div>sessionKey: {sessionKey}</div>
</>
)
}
export function getStaticPaths() {
return { paths: [], fallback: true }
}
export async function getStaticProps({ params }) {
const surveyId = params.survey[0]
const responseId = params.survey[1]
const data = { method: 'get_session_key', params: ['admin', 'password'], id: 1 }
const options = {
headers: {
'connection': 'keep-alive',
'content-type': 'application/json'
}
}
const url = 'https://example.com'
const sessionKey = await axios.post(url, data, options).then(
(response) => {
return response.data.result
},
(error) => {
console.log(error)
}
)
return {
props: { surveyId, responseId, sessionKey },
revalidate: false,
}
}

Pass data to getServerSideProps from previous page in NextJS

I am developing an e-commerce like website using NextJS.
I will fetch & display list of products in /products page. On clicking any product, I'll navigate to /details/[productId], and I'll fetch those product details as follows.
// In /details/[productId].js file
export async function getServerSideProps({params}) {
const res = await fetch(`https:my-api-url/api/products/${params.productId}`)
const product = await res.json()
return {
props: {
product
}
}
}
Problem
Everything looks good till this step. But I thought to reduce number of database read count, hence instead of fetching product detail again in detail page, I planned to use the data fetched in the previous page (/products) which will have the information about the product. Hence I need a way to pass those product object into next screen /details/[productId]'s getServerSideProps (to achieve SSR for SEO purposes).
Workaround
One solution I currently have is to stringify the product json and pass it via query parameter and get it back in getServerSideProps({params, query}). But it just spams my url in the browser which isn't look good at all.
Expectation
Is there any other way to pass the data into getServerSideProps function so that it will utilize the data to generate the whole page in server itself. Please guide me to overcome this issue. Any help would be appreciated.
Thanks in advance.. (:
You can bring in a custom server as express that provides locals property available through the lifetime of your application or request.
const next = require('next');
const express = require('express');
const app = next({ dev: process.env.NODE_ENV !== 'production' });
const handle = routes.getRequestHandler(app);
const env = process.env.NODE_ENV || 'dev';
app.prepare().then(() => {
const server = express();
server.get('/products', async (req, reply) => {
const products = await //... fetch product with details
req.app.locals.products = products;
return app.render(req, reply, '/path/to/products/page', req.query);
});
server.get('/details/:productId', async (req, reply) => {
const {productId} = req.params;
const {products} = req.app.locals;
// find product using productId and make available in req.locals
req.locals.product = // product;
return app.render(req, reply, '/path/to/product/detail/page', req.query)
});
server.get('*', (req, reply) => {
return handle(req, reply)
});
server.listen(3000);
});
Pay caution to how large your product list grow to avoid running your application out of memory.
You could also return a cookie containing the list of products on the request for products (See limits for HTTP cookies). Then read that on the product detail page.
When I enter URL http://localhost:3000/blog/wfe436
//getting the meta tags dynamically
export const getServerSideProps = async ({ params }) => {
// Get external data from the file system, API, DB, etc.
console.log(params) // here is the data of the url { blogname: 'wfe436' }
const posts = Data
// The value of the `props` key will be
// passed to the `Home` component
return {
props: { posts }
}
}

Resources