Optimize calls to exteral Api from getServerSideProps - next.js

Assume I have the following page in my next.js react application:
// Filename: [mypath].jsx
export default function MyPage(props) {
return (
<>
<Link href="/siteX">
<a>Go to siteX</a>
</Link>
<Link href="/siteY">
<a>Go to siteY</a>
</Link>
<div>{props.data.text}</div>
</>
);
}
export async function getServerSideProps(context) {
const mypath = context.params.mypath;
const res = await fetch(`https://external-api/${mypath}`)
const data = await res.json();
return { props: { data } };
}
When I access http://localhost:3000/siteX/ on server side the string siteX from the url is used for a call to an external (!) api on a different system, e.g. https://external-api/siteX. This works fine so far, but I see the following performance issue:
In the browser, when I click on a <Link>, two requests are happening: One to my own server to update getServerSideProps with the new path and a second one from my server to https://external-api/... to fetch the new data.
Do you see a way to optimize this? What I want is:
when clicking on <Link> there is only one request directly to https://external-api/... happening and data is updated directly (e.g. as a state of MyPage).
As it is now, when accessing http://localhost:3000/siteX/ the server should fetch the data and prerender the site
I could of course treat data as a state of <MyPage> and simply call a function to update it with a request when <Link> is clicked. But I also want a correct routing, history and so on.
Can you help me with that? Thank you!

After some more resesarch I came across a solution. The next.js <Link> component has a property shallow that I can set to prevent the execution of getServersideProps. That way I can query the new data manually when a link is clicked. Still the initial data query is done by getServersideProps on the server and my intial SSR is working as before.
// Filename: [mypath].jsx
export default function MyPage(props) {
const [data, setData] = useState(props.data);
function updateData(path) {
const res = await fetch(`https://external-api/${path}`)
const data = await res.json();
setData(data);
}
return (
<>
<!-- Manually query and set data -->
<div onClick={() => updateData("siteX")}>
<!-- Trigger routing, but don't execute getServerSideProps (because of shallow={true}) -->
<Link href="/siteX" shallow={true}>
<a>Go to siteX</a>
</Link>
</div>
<div onClick={() => updateData("siteY")}>
<Link href="/siteY" shallow={true}>
<a>Go to siteY</a>
</Link>
</div>
<div>{props.data.text}</div>
</>
);
}
// If page is requested directly via url (and not because a <Link> element has been clicked) do SSR as before
export async function getServerSideProps(context) {
const mypath = context.params.mypath;
const res = await fetch(`https://external-api/${mypath}`)
const data = await res.json();
return { props: { data } };
}

Related

React Query - useQuery callback dependent on route parameter? [duplicate]

When page is refreshed query is lost, disappears from react-query-devtools.
Before Next.js, I was using a react and react-router where I would pull a parameter from the router like this:
const { id } = useParams();
It worked then. With the help of the, Next.js Routing documentation
I have replaced useParams with:
import { usePZDetailData } from "../../hooks/usePZData";
import { useRouter } from "next/router";
const PZDetail = () => {
const router = useRouter();
const { id } = router.query;
const { } = usePZDetailData(id);
return <></>;
};
export default PZDetail;
Does not work on refresh. I found a similar topic, but manually using 'refetch' from react-query in useEffects doesn't seem like a good solution. How to do it then?
Edit
Referring to the comment, I am enclosing the rest of the code, the react-query hook. Together with the one already placed above, it forms a whole.
const fetchPZDetailData = (id) => {
return axiosInstance.get(`documents/pzs/${id}`);
};
export const usePZDetailData = (id) => {
return useQuery(["pzs", id], () => fetchPZDetailData(id), {});
};
Edit 2
I attach PZList page code with <Link> implementation
import Link from "next/link";
import React from "react";
import TableModel from "../../components/TableModel";
import { usePZSData } from "../../hooks/usePZData";
import { createColumnHelper } from "#tanstack/react-table";
type PZProps = {
id: number;
title: string;
entry_into_storage_date: string;
};
const index = () => {
const { data: PZS, isLoading } = usePZSData();
const columnHelper = createColumnHelper<PZProps>();
const columns = [
columnHelper.accessor("title", {
cell: (info) => (
<span>
<Link
href={`/pzs/${info.row.original.id}`}
>{`Dokument ${info.row.original.id}`}</Link>
</span>
),
header: "Tytuł",
}),
columnHelper.accessor("entry_into_storage_date", {
header: "Data wprowadzenia na stan ",
}),
];
return (
<div>
{isLoading ? (
"loading "
) : (
<TableModel data={PZS?.data} columns={columns} />
)}
</div>
);
};
export default index;
What you're experiencing is due to the Next.js' Automatic Static Optimization.
If getServerSideProps or getInitialProps is present in a page, Next.js
will switch to render the page on-demand, per-request (meaning
Server-Side Rendering).
If the above is not the case, Next.js will statically optimize your
page automatically by prerendering the page to static HTML.
During prerendering, the router's query object will be empty since we
do not have query information to provide during this phase. After
hydration, Next.js will trigger an update to your application to
provide the route parameters in the query object.
Since your page doesn't have getServerSideProps or getInitialProps, Next.js statically optimizes it automatically by prerendering it to static HTML. During this process the query string is an empty object, meaning in the first render router.query.id will be undefined. The query string value is only updated after hydration, triggering another render.
In your case, you can work around this by disabling the query if id is undefined. You can do so by passing the enabled option to the useQuery call.
export const usePZDetailData = (id) => {
return useQuery(["pzs", id], () => fetchPZDetailData(id), {
enabled: id
});
};
This will prevent making the request to the API if id is not defined during first render, and will make the request once its value is known after hydration.

Prevent useEffect infinite loop when updating NextJs query string

I have a tag input that produces an array of tags. Using NextJs's useRouter, I want to add those tags as query string params as they are added. I also need to preserve the current query string params, since other filters, searches, and pagination need to remain.
Here is how I'm currently doing it.
const router = useRouter();
const { query } = router;
const [tags, setTags] = useState([]);
useEffect(() => {
router.push({
query: {
...query,
tags,
},
});
}, [tags, router, query]);
return (
<>
<TagInput tags={tags} setTags={setTags} placeholder="Search by tags" />
</>
);
However, this causes an infinite render since the useEffect updates the query but also has query as a dependency. If I remove query as a dependency, it works fine, but I get the missing dependency linting error.
Edit: Here is a codesandbox with a minimal example that reproduces the issue. It works as is, but if you uncomment the query dependency, infinite loop.
https://codesandbox.io/s/next-js-dynamic-routing-forked-rlxuqh?file=/pages/index.js
One way this can be done is to directly update the query object and read the tags from the url, rather than storing the tags in state.
const router = useRouter();
const { query } = router;
const setTags = useCallback((tags) => {
router.push({
query: {
...query,
tags
}
});
}, [router, query]);
return (
<>
<TagInput tags={query.tags || []} setTags={setTags} placeholder="Search by tags" />
</>
);

NextJS SSR with context on refresh

I am trying to create a page with that goes like this - Each node can have X number of pods and when the user clicks on a particular node, he will be navigated to a page displaying all the pods belonging to this node.
I have managed to do so using dynamic routing from NextJS.
<Link
href={{
pathname: "/pods/[id]",
query: { nodeID: row.ID },
}}
as={`/pods/${row.ID}`}
>
<StyledButton>View Pods</StyledButton>
</Link>
const Pods = ({ pods }) => {
return (
<>
<PodTable pods={pods}></PodTable>;
</>
);
};
export async function getServerSideProps(context) {
const nodeID = context.query.nodeID;
// Fetch data from external API
const podRes = await fetch(`http://localhost:5000/v1/pods/filter/${nodeID}`);
const pods = await podRes.json();
console.log(`Pods Res: ${podRes}`);
// Pass data to the page via props
return { props: { pods } };
}
export default Pods;
While this generally works, the issue arise when the user navigates directly to the /pod/[id] page or refreshes the page without clicking the link button.
How should I fix my code to address this?

Next.js: How to create multiple dynamic pages coming from different types of API?

This is my first experience with Next.js. I am trying to create a dynamic route from the data coming from server.
I do convert the id to string but have the same error.
Server Error
Error: A required parameter (articleid) was not provided as a string in getStaticPaths for /article/[articleid]
I tried something similar with the data from web api it works fine but not for the data that I fetch from server. Can't figure out what I am missing.
Also why error message is pointing out server error in the first line?
Here is the component the error is coming from: pages/article/[articleid]/index.js
export const getStaticProps = async (context) => {
const res = await fetch(`${server}/api/article/${context.params.id}`);
const article = await res.json();
return {
props: {
article,
},
};
};
export const getStaticPaths = async () => {
const res = await fetch(`${server}/api/article/`);
const articles = await res.json();
const ids = articles.map((article) => article.id);
const paths = ids.map((id) => ({ params: { id: id.toString() } }));
return {
fallback: "blocking",
paths: paths,
};
};
`pages/api/article/[id].js file
import { articles } from "../../../data";
export default function handler({ query: { id } }, res) {
const filteredData = articles.filter((article) => article.id === id);
if (filteredData.length > 0) {
res.status(200).json(filteredData[0]);
} else {
res.status(404).json({ message: `article with id of ${id} is not found` });
}
}
UPDATE
I found out that my problem is definitely not from the code provided above.
Actually in my app I have another dynamic page where I fetch the data from another web api, which works fine. Changing the urls I found out that now web api dynamic page is throwing the same error. I assume the problem is that how I defined the paths, , [articleid] I mean.
Here is my components structure
The problem is with `pages/article/[articleid].
Here is how I am linking to the specific item
<Link href="/article/[articleid]" as={`/article/${article.id}`}>
<a className={styles.container}>
<h1>{article.title} →</h1>
<p>{article.body}</p>
</a>
</Link>
Any help will be appreciated
Actually I made it work changing the dynamic route id name in brackets, in my case [articleid], to just [id] and it worked fine. Image below.
But honestly, I didn't understand why the previous keyword ([articleid]) in brackets was not working.
I also tried another keywords inside the brackets for dynamic route like [article] and nothing worked except [id].
I didn't find anything related in Next.js docs about that.
I'd welcome any explanations why only [id] worked.

React Query and using queryClient in both _app.js AND getInitialProps

So, in the docs, we have this code:
https://react-query.tanstack.com/guides/ssr#using-hydration
RE: The use of 'useRef' to store a reference.
// _app.jsx
import { QueryClient, QueryClientProvider } from 'react-query'
import { Hydrate } from 'react-query/hydration'
export default function MyApp({ Component, pageProps }) {
const queryClientRef = React.useRef()
if (!queryClientRef.current) {
queryClientRef.current = new QueryClient()
}
return (
<QueryClientProvider client={queryClientRef.current}>
<Hydrate state={pageProps.dehydratedState}>
<Component {...pageProps} />
</Hydrate>
</QueryClientProvider>
)
}
BUT, I also need to store some fetch calls IN the "cache" in the MyApp.getInitialProps.... how is that gonna happen IF I create an instance with useRef in function above? Meaning, how is my "getInitialProps" gonna get that instance?
MyApp.getInitialProps = async (appContext) => {
// in here, I do a fetch and get some data I need for SSR
// user Session etc...
const { user } = await fetchUserSession();
// WHAT INSTANCE IS THIS?
queryClient.setQueryData('user', user || {});
return {
...appProps,
dehydratedState: dehydrate(queryClient),
}
}
I am currently defining queryClient = new QueryClient() at the top of the page, so "both" can use it. But I think that is causing some issues with hydration when I npm run build this app.
Remember, this is in "_app.js" so I have to use getInitialProps.
The reason I am doing it here is because we need the users session sitewide, no matter what page they and on. So, rather than do this in every single /page/, just do it in _app.js, so the whole site needs that? The /page/ are Static Generated.
for prefetching on the server, you just create a new QueryClient like described further down on the page you have linked:
export async function getStaticProps() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery('posts', getPosts)
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
}
Here, you create a new empty client, prefetch and take the state and dehydrate it. Then, on the frontend, that state is put into your instance client from MyApp. This is just a way of getting the data from that server-side cache into the client-side cache.

Resources