nextjs reload page with Link component - next.js

I have a simple nextjs 13 application (appDir) with 2 pages and a <Link/> component navigation.
first page Home - static
second page Test - receiving dynamic random content on the server side (fetch) from a third-party source.
Problem: When the application is rendered everything works fine, but when I try to switch between pages, my test page shows the old content, I can refresh the browser to get the actual data, is very similar to navigating through regular links <a/>, but i need without reloading the entire application.
Q: How can I force nextjs 13 to reload the Test page when I switch between pages with <Link/> component?
// src/components/navbar.tsx
'use client'
import {usePathname} from "next/navigation";
import Link from "next/link";
const navItems = [
{text: 'Home', href: '/'},
{text: 'Test', href: '/test'}
];
const Navbar = () => {
const pathname = usePathname();
return <nav className="nav nav-masthead justify-content-center float-md-end">
{navItems.map((item: { text: string, href: string, link?: boolean }, idx: number) => (
<Link key={idx} href={item.href} className={`nav-link${item.href === pathname ? ' active' : ''}`}>
{item.text}
</Link>
)
)}
</nav>
}
export default Navbar;
// src/app/test/page.tsx
import * as crypto from "crypto";
const getData = async () => {
const res = await fetch('http://localhost:3000/random-data', {cache: 'no-store'});
if (!res.ok) {
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
return <p>{crypto.createHash('sha256').update(JSON.stringify(await getData())).digest('hex')}</p>
};

I've recently asked about this same topic on their feedback discussion on github: https://github.com/vercel/next.js/discussions/41745?sort=new#discussioncomment-4620262
The cause of the problem is Link is only doing client side navigation and seems to serve a cached state of the previously visited component. You'll notice that the client never calls back to the server and thus the server component never runs the second time.
I've been searching for days, but haven't found a way to force Link to reload or force refresh the component to re-render.
My conclusion is that if you have dynamic data that needs to refreshed periodically, it's best to render it in a client component and not use a server component for now.
Also, if you'd like to use Suspense, you'll need to use a library like SWR or React Query for any client side data fetching.

Related

Next.js SSG page component gets new object from props every time it renders

I'm using Next.js and i want to build simple SSG page, which take immutable data from db on build time and render it to some list of elements. For that purpose i'm using getStaticProps. Here's source code of this page:
import Layout from '../components/Layout'
import utilStyles from '../global-styles/utils.module.css'
import { getSortedPostsData } from '../utils/posts'
import { useMemo } from 'react'
const Home = ({ allPostsData }) => {
const posts = useMemo(() => {
console.log('useMemo log');
return allPostsData.map(({ id, date, title }) => (
<li className={utilStyles.listItem} key={id}>
{title}
<br />
{id}
<br />
{date}
</li>
))
}, [allPostsData])
return (
<Layout home>
<ul className={utilStyles.list}>
{posts}
</ul>
</Layout>
)
}
export const getStaticProps = async () => {
console.log('getStaticProps run')
const allPostsData = await getSortedPostsData()
return {
props: {
allPostsData
}
}
}
export default Home
In production build I can see 'getStaticProps run' log once in console. All required data gets from database and passes to Home component in props. This component renders posts list fine. When i switch to this page in my browser first time I can see exactly one 'useMemo log' in the console. But...
Every time I switch to another page and then back to Home page I see another 'useMemo log' in the browser console too. I'm not understand why this is happening. This page loaded exactly once (not on every page switch). Data for this page is obtained exactly once (in build time). But Next.js passes new array (array with new address) in Home page props every time it renders. Why this is happening and how can i avoid this behavior to memoize my posts list and not render it every time I switch the page?

Storyblok React Bridge Restarting Preview On Data Change

I want to use the new Storyblok React bridge with Next.js. In my case, there is a component directly in the _app.js (which should not change between route changes), so it looks like this:
function MyApp({ Component, pageProps: { pageData, globalData } }) {
const story = useStoryblokState(globalData.story)
console.log('rendering app')
return (
<>
<Collage story={story} />
{/* <Component {...pageData} /> */}
</>
);
}
The corresponding getStaticProps looks like this:
export async function getStaticProps() {
let sbParams = {
version: "draft", // or 'published'
};
const storyblokApi = getStoryblokApi();
let { data: pageData } = await storyblokApi.get(`cdn/stories/home`, sbParams);
let { data: globalData } = await storyblokApi.get(
`cdn/stories/collage`,
sbParams
);
return {
props: {
pageData,
globalData,
},
revalidate: 3600,
};
}
Here everything works as intended (i.e. changes to data will only rerender the collage component but not touch anything else), but as soon as I uncomment the main component, in the Storyblok preview (with localhost:3000), changing any data will restart the iFrame, just like it would be the case if no React bridge is used. I can even see that the data changes are applied just before it restarts. Any idea how to fix this?
useStoryblokState() calls useStoryblokBridge() from the #storyblok/js package. Despite its name, useStoryblokBridge() is not a react hook, but an ordinary function with side effects. It subscribes to events from the Storyblok bridge here. If the story that was passed as argument isn't the same story that is sent to the event listener via the Storyblok bridge, the event listener will reload the entire page.
https://github.com/storyblok/storyblok-js/blob/main/lib/index.ts#L40
This means that if you call useStoryblokBridge() with two different stories, the window will reload.

Why I don't see div in html (view page source in browser) when I use getStaticProps in next.js

I try to make test SSR app based on Next.js + React + Apollo.
But I cannot understand how SSR works.
I read the docs and I found that to get some important data while first SSR render we need use getStaticProps and the result of function call will be passed to the props of component which I try to render.
Depends on this props I can render whatever I want, for example I have next code:
import React from "react";
import {GetServerSideProps, GetStaticProps} from "next";
const Home = (props) => {
return (
<div>{props.ValFromGetStaticProps}</div>
)
};
export const getStaticProps: GetStaticProps = async (context) => {
return { props: {ValFromGetStaticProps: 'ValFromGetStaticProps!'} }
};
export default Home;
I expect to see rendered this code on the server side, and if I open sources of the HTML I should see this div. But instead I see just some props object... And there is no div ((
Remark: in the DOM this div present, but for SEO purpose this div should exists in the page source.
Ohh... I didn't see it because I blocked first render early in the parent component:
_app.tsx
export default function ({ Component, pageProps }) {
<BackendDataProvider>
<Component {...pageProps}>
</BackendDataProvider>
}
in the BackendDataProvider was condition
const {data, error, loading} = useQuery(BACKEND_QUERY)
if (error || loading) {
return null;
}
so that was a reason why first render was not rendered correctly

How to Manage a Navigation Menu in NextJS with WordPress

I'm building a NextJS app using headless WordPress with GraphQL. It's not clear from the documentation where I should be calling the query to create the site navigation.
https://github.com/lfades/next.js/tree/examples/cms-wordpress/examples/cms-wordpress
The navigation is controlled dynamically by WordPress Menus (Appearance > Menus) on the backend and I can successfully access these menuItems via GraphQL without any issue on the index.js and posts/[slug].js page templates in Next JS.
// index.js
export default function Index({ primaryMenu = [] }) {
return (
<Layout>
<Header>
{primaryMenu.map((item, index) => {
return (<a href={item.url}>{item.label}</a>)
)}
</Header>
</Layout>
);
}
export async function getStaticProps() {
const primaryMenu = await getPrimaryMenu(); // Get menu via GraphQL
return {
props: { primaryMenu },
};
}
The issue I'm having with this is I am repeating the getStaticProps function on each template and I should be able to use some sort of global query for this, either in the <header/> component itself or another method. I'm unable to find documentation on how to do this and it doesn't work in components.
Any guidance (or examples) on where a global query such as a dynamic Navigation query would live in a NextJS app is appreciated.
There are a couple of ways you can do it:
You can menuItems query with useQuery() from #apollo/client inside the Layout component so that its available to all pages which are wrapped inside the Layout. However the problem with this is that, there will be a load time and the data won't be prefetched and readily available like we can do with getServerSideProps() ( at page level ). Because this will be at component level.
import { useQuery } from "#apollo/client";
export default function Layout () {
const { loading, data } = useQuery( GET_MENU_QUERY )
return {...}
}
You can use swr that uses caching strategy. There is blog that explains how to use it
I battled this for a while (for JD site) with redux and wp rest, but I think theory should be the same for gql + apollo client.
You need to override Next App _app with a custom class that extends App.
And you might need to inject an instance of apollo client into AppContext using a HOC. I used this wrapper for Redux. Would need to be modelled after that.
Edit: (Looks like someone has made it already)
// export default withRedux(makeStore)(MyApp);
export default withApollo(apolloClient)(MyApp); ???
Then in your App getInitialProps, you can make query to get menu. By default apollo client query will grab cached value if it's in the cache store already I believe.
static async getInitialProps(appContext) {
const { isServer, pathname, apollo? } = appContext.ctx;
// do menu query
const menu = apollo.query???
// Redux version
// const state = store.getState();
// let main_menu = state.menu;
// if (!state.menu) {
// const menu = await apiService().getMenu("main");
// main_menu = menu;
// store.dispatch({ type: "SET_MENU", payload: menu });
// }
...
// call the page's `getInitialProps` and fills `appProps.pageProps`
const initialProps = await App.getInitialProps(appContext);
const appProps: any = {
...initialProps,
menu: main_menu
};
return appProps;
}
Now menu is in the page props of the App Component, which can be passed down.
Or you can use apollo client to make the query again in a child component. So when you make the query again, in header or whatever, it will take the cached response provided it's the same query.
I made an endpoint for menus that included the template name + post slug along with the menu items and mapped the wp templates to next routes.
const menu = useSelector((state: any) => state.menu);
const menuItems = menu.map((item: any) => {
const path = getTemplatePath(item.template);
return (
<Link key={item.slug} href={`/${path}`} as={`/${item.slug}`} scroll={false}>
<a>{item.title}</a>
</Link>
);
});

NextJS server side renders even without getXProps?

This code is in a page file in NextJS. Although I'm not using getStaticProps or getServerSideProps it still performs server side rendering.
Is this by design? The docs would imply that these get functions are required: https://nextjs.org/docs/basic-features/data-fetching
import React from "react";
import Head from "next/head";
import Link from "next/link";
import { ApolloProvider } from "#apollo/react-hooks";
import ApolloClient from "apollo-boost";
import { gql } from "apollo-boost";
import { useQuery } from "#apollo/react-hooks";
const client = new ApolloClient({
uri: "https://48p1r2roz4.sse.codesandbox.io",
});
const EXCHANGE_RATES = gql`
{
rates(currency: "USD") {
currency
rate
}
}
`;
const Home: React.FC = () => {
const { loading, error, data } = useQuery(EXCHANGE_RATES);
if (loading) {
return (
<div>
<p>Loading</p>
</div>
);
}
if (error) {
return (
<div>
<p>Error</p>
</div>
);
}
return (
<div>
<ul>
{data.rates.map((item) => (
<li key={item.currency}>
{item.currency} - {item.rate}
</li>
))}
</ul>
</div>
);
};
export default () => (
<ApolloProvider client={client}>
<Home />
</ApolloProvider>
);
Production mode
The page provided has no getInitialProps or getServerSideProps. It's statically optimized (pre-rendered at build time).
So, when you request this page in a browser, you will see a pre-rendered HTML content in the response. Disabling JavaScript does not affect it.
If you navigate to the page by using client-side next/link or router the page will be rendered at client-side without making a request to a server.
Development mode
In Development mode this page would be both - server-side rendered and client-side rendered.
If you'd request the page by typing address in a browser it will be pre-rendered at server-side.
If you navigate to the page by using client-side next/link or router the page will be rendered at client-side without making a request to a server (you will see only Webpack Hot Module Replacement request).

Resources