We have a simple setup involving a Context API provider wrapping the _app.tsx
page. The flow we're trying to implement is:
getServerSideProps
_app.tsx
as mentioned above)So we have a pretty standard setup that works correctly on the client side. The problem is that the updated context values are not being used to SSR the pages.
const ContextValue = createContext();
const ContextUpdate = createContext();
export const ContextProvider = ({children}) => {
const [context, setContext] = useState({});
return <ContextValue.Provider value={context}>
<ContextUpdate.Provider value={setContext}>
{children}
</ContextValue.Provider>
</ContextUpdate.Provider>
}
export const useContextValue = () => {
const context = useContext(ContextValue);
return context;
}
export const useContextUpdate = () => {
const update = useContext(ContextUpdate);
return update;
}
Then we have on _app.jsx
:
...
return <ContextProvider>
<ContextApiConsumingComponent />
<Component {...pageProps} />
</Context>
And in any page, if we can update the context by using the hook provided above. For example:
export function Index({customData, isSSR}) {
const update = useContextUpdate();
if(isSSR) {
update({customData})
}
return (
<div>...</div>
);
}
export async function getServerSideProps(context) {
...
return {
props: { customData , isSSR}
};
};
And we can consume the context in our ContextApiConsumingComponent
:
export function ContextApiConsumingComponent() {
const context = useContextValue()
return <pre>{JSON.stringify(context?.customData ?? {})}</pre>
}
The (very simplified) code above works fine on the client. But during the SSR, if we inspect the HTML sent to the browser by the server, we'll notice that the <pre></pre>
tags are empty even though the context values are correctly sent to the browser in the __NEXT_DATA__
script section (they are precisely filled with the data on the browser after the app is loaded).
I've put together a GitHub repo with a minimum reproduction too.
Am I missing something?
Not sure if you have other reasons for using the context to get this data into the api consumer but with nextjs, the getStaticProps
or getServerSideProps
of course feeds into the route component, but maybe you've missed the following detail?
The Component prop is the active page, so whenever you navigate between routes, Component will change to the new page. Therefore, any props you send to Component will be received by the page.
pageProps
is an object with the initial props that were preloaded for your page by one of our data fetching methods, otherwise it's an empty object.
https://nextjs.org/docs/advanced-features/custom-app
Therefore the _app.tsx
can act on any props your route components would normally receive from getStaticProps
or getServerSideProps
types.ts
// or whatever union type you need to indicate that myriad of "custom datas" you might be pushing out of getStaticProps
export type MaybeCustomDataFromGetStaticProps = Map<string, string> | undefined;
export type PagePropsWithCustomData = {
customData: MaybeCustomDataFromGetStaticProps
isSSR?: boolean;
}
pages/_app.tsx
import type { PagePropsWithCustomData } from '../types';
const App = ({
Component,
pageProps: { customData, isSSR, ...pageProps},
router,
}: AppProps<PagePropsWithCustomData & Record<string, unknown>>) => {
return (
<ContextProvider value={{ isSSR, customData }}>
<ContextApiConsumingComponent />
<Component {...pageProps} />
</ContextProvider>
)
}
pages/somepage.tsx
import type { NextPage, GetServerSideProps } from "next";
import type { PagePropsWithCustomData } from '../types';
import { magicallyGetCustomData } from '../services/Api';
import { isSSR } from '../core/Routing';
type IndexRouteProps = PagePropsWithCustomData & {
isSSR?: boolean;
}
const IndexRoute:NextPage<IndexRouteProps> = ({ customData, isSSR }) => {
// no longer need to do anything with customData, or isSSR here
return (
<div>stuff</div>
);
}
export const getServerSideProps:GetServerSideProps<IndexRouteProps> = async (context) => {
const customData = await magicallyGetCustomData();
return {
props: { customData , isSSR: isSSR(context) }
};
};
export default IndexRoute