I'm building a Next.js headless application, where I'm getting the data via API calls to an Umbraco backend. Im using getServerSideProps to load the data for each of my pages, which then is passed as "data" into the functional component and into the page.
The issue I have is that I have separate endpoints for the header / footer portion of the website, and is shared across all pages. Thus, it is a shame, and bad practice to do 3 calls per page (header, data, footer).
What could be done, in order to get header / footer once, then keep it across multiple pages, while maintaining SSR? (important). I've tried using cookies, but they cannot hold so much data. Below is some code:
Page Fetching data:
export async function getServerSideProps({ locale }) {
const footer = await Fetcher(footerEndPoint, locale);
return {
props: {
locale: locale,
footer: footer
}
}
}
Layout
const Layout = (props) => {
const { children, footer } = props;
return (
<>
<Header />
<main>
{children}
</main>
<Footer footer={footer} />
</>
);
};
export default Layout;
I see three options to achieve SSR-only data fetching once for things that won't ever change between page transitions:
You can just use getInitialProps() in _app.tsx. This runs on the server first and you can just cache the response value in a variable. Next time getInitialProps() is executed, it will just serve the cached value instead of firing another request. To make this work client-side, you have to rehydrate the cache variable in an useEffect:
// pages/_app.tsx
let navigationPropsCache
function MyApp({ Component, pageProps, navigationProps }) {
useEffect(
()=>{
navigationPropsCache = navigationProps
},
[]
)
return <>
<Navigation items={navigationProps}/>
<Component {...pageProps} />
</>
}
MyApp.getInitialProps = async () => {
if(navigationPropsCache) {
return {navigationProps: navigationPropsCache}
}
const res = await fetch("http://localhost:3000/api/navigation")
const navigationProps = await res.json()
navigationPropsCache = navigationProps
return {navigationProps}
}
Note that getInitialProps() is a deprecated feature since next 9.3. Not sure how long this will be supported in the the future. See: https://nextjs.org/docs/api-reference/data-fetching/getInitialProps
See https://github.com/breytex/firat500/tree/trivial-getInitialProps for full code example.
This solution is based on two ideas:
<script>.// server/index.ts
server.all("*", async (req, res) => {
const html = await app.renderToHTML(req, res, req.path, req.query);
const navigationProps = await getNavigationProps()
const navigationHtml = renderToString(React.createElement(Navigation, {items: navigationProps}))
const finalHtml = html
.replace("</body>", `<script>window.navigationProps = ${JSON.stringify(navigationProps)};</script></body>`)
.replace("{{navigation-placeholder}}", navigationHtml)
return res.send(finalHtml);
});
// components/Navigation.tsx
export const Navigation: React.FC<Props> = ({items})=>{
const [finalItems, setFinalItems] = useState(items ?? [])
useEffect(
()=>{
setFinalItems((window as any).navigationProps)
},
[]
)
if(!Array.isArray(finalItems) || finalItems.length === 0) return <div>{"{{navigation-placeholder}}"}</div>
return (
<div style={{display:"flex", maxWidth: "500px", justifyContent: "space-between", marginTop: "100px"}}>
{finalItems.map(item => <NavigationItem {...item}/>)}
</div>
)
}
I'd consider this a pretty dirty example for now, but you could build something powerful based on this.
See full code here: https://github.com/breytex/firat500/tree/next-link-navigation
This example is a little bit longer and based on the outstanding work of the urql project: https://github.com/FormidableLabs/next-urql/blob/master/src/with-urql-client.tsx
See full example here: https://github.com/breytex/firat500/tree/prepass
I'd personally would go with option #1 as long as its feasible. #3 looks like an approach with a good developer experience, suitable for bigger teams. #2 needs some love to actually be useful :D
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With