How to enable ssg/ssr in next.js with custom App component - next.js

I have a custom _app.js which looks like this:
function MyApp({ Component, pageProps }) {
const Layout = Component.layoutProps?.Layout || React.Fragment
const layoutProps = Component.layoutProps?.Layout
? { layoutProps: Component.layoutProps }
: {}
const meta = Component.layoutProps?.meta || {}
const description =
meta.metaDescription || meta.description || 'Meta Description'
const store = useStore(pageProps.initialReduxState)
return (
<QueryClientProvider client={queryClient}>
<Provider session={pageProps.session}>
<Title suffix="My Dynamic Site">{meta.metaTitle || meta.title}</Title>
<Description>{description}</Description>
<Meta />
<ReduxProvider store={store}>
<PersistGate persistor={store.__PERSISTOR} loading={null}>
<CartBox />
<Layout {...layoutProps}>
<Component {...pageProps} />
</Layout>
</PersistGate>
</ReduxProvider>
<ReactQueryDevtools initialIsOpen={false} />
</Provider>
</QueryClientProvider>
)
}
MyApp.getInitialProps = async (appContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
return { ...appProps }
}
export default MyApp
Now, I would like to fetch data using ssg/ssr data fetching method to help SEO team for my page components.
But, it seems any of the methods aren't working as expected, none of them actually passing props to the page component.
Here's my page component.
const HomePage = ({ title, stars }) => {
console.log(title, stars); // undefined, undefined
return (
<div>
<Header title={title} />
<GhStars stars={stars} />
<Footer />
</div>
)
}
export const getStaticProps = async () => {
return {
props: {
title: "My Dynamic Title From getStaticProps"
}
}
}
// I tried both getInitialProps & getStaticProps independently.
HomePage.getInitialProps = async (ctx) => {
const res = await fetch('https://api.github.com/repos/vercel/next.js')
const json = await res.json()
return { stars: json.stargazers_count }
}
export default HomePage
I might be missing something for sure, which I failed to figure out so far.
Any help will be really much appreciated. Thanks.

Related

Why is my server storage not syncing with the client one in Next.js app?

Problem: My next.js app crash on client side because of empty store object, but if I try to read this object in getServerSideProps it`s ok.
I have 2 pages in my app, profile/[id] and post/[id], all of them have getServerSideProps
User flow:
User coming on profile/[id] by friend`s link
On profile/[id] page he has profile data and 3x3 posts grid, every post is a link to post/[id]
Click on post
Navigate to post/[id] - here he has some post data: username, image, createdAt etc...
Expected: Server render html for post page after successful request
Received: Client crash after trying to read field of empty object
Question: Can you tell my what's wrong with my code? I have HYDRATE for postSlice and default next-redux-wrapper code so I'm confused.
Code:
store.ts
import {configureStore} from "#reduxjs/toolkit";
import {createWrapper} from "next-redux-wrapper";
import profileSlice from './reducers/profileSlice';
import postsSlice from './reducers/postsSlice';
import postSlice from './reducers/postSlice';
export const makeStore = () =>
configureStore({
reducer: {
profile: profileSlice,
posts: postsSlice,
post: postSlice
},
devTools: true
});
export type Store = ReturnType<typeof makeStore>;
export type RootState = ReturnType<Store['getState']>;
export const wrapper = createWrapper<Store>(makeStore);
_app.tsx
//
...imports
//
function App({Component, ...rest}: AppProps) {
const {store, props} = wrapper.useWrappedStore(rest);
const {pageProps} = props;
return (
<Provider store={store}>
<ApolloProvider client={client}>
<GlobalStyle/>
<ThemeProvider theme={theme}>
<LookyHead />
<Component {...pageProps} />
</ThemeProvider>
</ApolloProvider>
</Provider>
);
}
export default App;
postSlice.ts
//
some imports and interfaces...
//
export const postSlice = createSlice({
name: 'post',
initialState,
reducers: {
setPost: (state, action) => {
state.post = action.payload
}
},
extraReducers: {
[HYDRATE]: (state, action) => {
return {
...state,
...action.payload.post,
};
},
},
});
export const { setPost } = postSlice.actions;
export default postSlice.reducer;
Post component of posts grid on profile, here i have link to post/[id]
function Post({previewUrl, likesCount, commentsCount, duration, id}: Props) {
some code...
return (
<Link href={`/post/${id}`}>
<Container
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
>
<img src={previewUrl} alt="image"/>
<PostFooter
isHover={isHover}
likesCount={likesCount}
commentsCount={commentsCount}
time={time}
/>
</Container>
</Link>
);
}
export default memo(Post);
getServerSideProps in post/[id]
export const getServerSideProps =
wrapper.getServerSideProps(
(store) =>
async ({query}) => {
const id = query!.id as string
try {
const {data} = await client.query<Res, Vars>({
query: GET_POST,
variables: {
postId: id
}
});
console.log(data.publicPost) // Here I have data!
store.dispatch(setPost(data.publicPost))
} catch (e) {
console.log(e)
}
return {
props: {}
}
})
export default Post;
Data component inside post/[id], where client crash
//
imports...
//
function Data() {
const {post} = useAppSelector(({post}) => post) // looks weird but its ok
const parsed = parseISO(post?.createdAt) // Here my client fall
const date = format(parsed, 'dd MMMM yyyy, HH:MM', {locale: enGB})
return (
...
);
}
export default Data;

Nextjs dyanmic router callback sometimes undefined

here is my folder stucture
shop
- [categorySlug]
- [productSlug]
- index.tsx
- index.tsx
index.tsx
graphql calling will be
/shop/shoes/nike-1
/shop/jersey/jersey-1
[categorySlug] index.tsx
export function ShopCategoryComponent({ products }): JSX.Element {
const { query } = useRouter();
const router = useRouter();
const categorySlug = hasCategorySlug(query) ? query.categorySlug : undefined;
const viewProductDeatails = useCallback(
(productSlug) => {
router.push(`/shop/${categorySlug}/${productSlug}`);
},
[router]
);
return (
<section>
{products.map((product) => (
<div
key={`${product.id}`}
onClick={() => viewProductDeatails(product.slug)}
>
<h3>{product.name}</h3>
<h3>{product?.$on['SimpleProduct'].price()}</h3>
<img src={product?.$on['SimpleProduct'].image.sourceUrl()} />
</div>
))}
</section>
);
}
export default function ShopCategory() {
const { useQuery } = client;
const { query } = useRouter();
const { products } = useQuery();
const categorySlug = hasCategorySlug(query) ? query.categorySlug : undefined;
const generalSettings = useQuery().generalSettings;
const setProducts = products({
where: {
categoryIn: categorySlug,
},
}).nodes;
return (
<React.Fragment>
<Menu />
<main className='content content-single'>
<ShopCategoryComponent products={setProducts} />
</main>
<Footer copyrightHolder={generalSettings.title} />
</React.Fragment>
);
}
when i click on the image it will then go to the productSlug page to show single product detail, it work sometimes, sometimes it will be undefined, like this.i wonder where i did wrong to cause this ?
/shop/shoes/undefined
it happen because of re-hydration and the categorySlug and productSlug have not get slug and it show undefined. also i mess up my variable categorySlug.
1.change
const categorySlug = hasCategorySlug(query) ? query.categorySlug : undefined;
to
const categorySlug = query.categorySlug
2.add
export function ShopCategoryComponent({ products }): JSX.Element {
const { query, isReady } = useRouter();
const categorySlug = query.categorySlug;
// pre-render page check, show loading if data havet get it.
if (!isReady) {
return (
<div>
<h1>Loading...</h1>
</div>
);
}
}
now everything work fine now. thanks user8566930

getInitialProps returning undefined

I'm using getInitialProps on _app to grab cookies and pass the values down to a component to set an element's width, but it's always sending undefined.
Here is the _app page where I'll be fetching the cookies
// _app.tsx -> page
function MyApp({ Component, pageProps }: AppProps) {
return (
<Layout {...pageProps}>
<Component {...pageProps} />
</Layout>
);
}
MyApp.getInitialProps = async (appContext) => {
const pageProps = App.getInitialProps(appContext);
const { req } = appContext.ctx;
const cookies = parseCookies(req);
return {
...pageProps,
initialAsideLeftHandler: cookies.asideLeftHandler
};
}
export default MyApp;
Pass it down to layout component for further use in asideLeft component
// Layout.tsx -> component
interface Props {
initialAsideLeftHandler: {
x: number,
y: number,
width:number,
height:number
},
children: JSX.Element[] | JSX.Element
}
const Layout = (props: Props) => {
const { children } = props;
const [isAsideRightOpen, setIsAsideRightOpen] = useState(true);
return (
<>
<AsideLeft initialAsideLeftHandler={props.initialAsideLeftHandler}>
</AsideLeft>
/* everything else */
<>
)
}
Component where I'll be using the fetched cookies from server but unfortunately as I parse it, it returns an error: Unexpected token u in JSON at position 0
// asideLeftHandler.tsx -> component
function AsideLeft({initialAsideLeftHandler}) {
const router = useRouter();
const state = JSON.parse(initialAsideLeftHandler)
const [asideLeftHandler, setAsideLeftHandler] = useState(JSON.parse(initialAsideLeftHandler)); // ---> line of error: Unexpected token u in JSON at position 0
useEffect(() => {
Cookie.set("asideLeftHandler", JSON.stringify(asideLeftHandler), { expires: 7});
}, [asideLeftHandler]);
return (
<Resizeable
width={asideLeftHandler.width}
>
/* everything else */
)
}
Your _app should be like this
function MyApp({ Component, pageProps, initialAsideLeftHandler }: AppProps) {
return (
<Layout initialAsideLeftHandler={initialAsideLeftHandler}>
<Component {...pageProps} />
</Layout>
);
}
MyApp.getInitialProps = async (appContext) => {
const pageProps = App.getInitialProps(appContext);
const { req } = appContext.ctx;
const cookies = parseCookies(req);
return {
...pageProps,
initialAsideLeftHandler: cookies.asideLeftHandler
};
}

React-query with NextJs routing

I have a page with few queries and it's all working until I update the URL params and then react-query stops working and also disappear from dev-tools.
When I click on a row it triggers the handleRowClick function which update the URL with params, and then react query stops working.
1.First Image
2.Clicking on a row ( Update URL params ) Second Image
const Volumes: NextPage = () => {
const apiRef = useGridApiRef()
const [volumes, setVolumes] = useState<IDataset[] | undefined>(undefined)
const [isOpen, setOpen] = useState(false)
const [rightWingData, setRightWingData] = useState<IDataset | undefined>(undefined)
const [searchValue, setSearch] = useState('')
const [volumeId, setVolumeId] = useState('')
const { isLoading, data } = useVolumeData()
const { isLoading: searchIsLoading, data: searchData } = useSearchVolumes(searchValue)
const { isLoading: volumeByIdLoading, data: volumeByIdData } = useVolumeDataById(volumeId)
const router = useRouter()
useEffect(() => {
if(router.isReady && router.query?.id && !rightWingData){
const volumeId = router.query.id.toString()
setVolumeId(volumeId)
}
if (!isLoading && data && !searchData) {
setVolumes(data.data.result)
}
else if (!searchIsLoading) {
setVolumes(searchData)
}
if(!volumeByIdLoading && volumeByIdData){
showVolumeData(volumeByIdData)
}
}, [data, isLoading, searchIsLoading, searchData, isOpen, rightWingData, volumeByIdLoading, volumeByIdData])
const handleRowClick = (rowData: IRowData) => {
showVolumeData(rowData.row)
Router.push({
pathname: '/volumes',
query: { id: rowData.row.id},
})
}
const showVolumeData = (volumeData: IDataset) => {
apiRef.current.updateColumns(thinColumns)
setRightWingData(volumeData)
setOpen(true)
setVolumeId('')
}
const closeRightWing = () => {
setOpen(false)
Router.push('/volumes')
apiRef.current.updateColumns(columns)
}
if (isLoading || !volumes) return <h1>Is Loading...</h1>
return (
<div className={`volumes ${isOpen ? "open" : "close"}`}>
<div className="volumes-table">
<InfTable setSearch={setSearch} searchValue={searchValue} apiRef={apiRef}
rows={volumes} columns={columns} initialState={initialState} onRowClick={handleRowClick} />
</div>
{rightWingData &&
<div className="right-wing-wrapper" >
<VolumeRightWing volume={rightWingData} onClose={closeRightWing} />
</div>
}
</div>
)
}
function MyApp({ Component, pageProps }: AppProps) {
const queryClient = new QueryClient()
return (
<QueryClientProvider client={queryClient}>
<Layout>
<Component {...pageProps} />
<ReactQueryDevtools initialIsOpen={false} position="bottom-right" />
</Layout>
</QueryClientProvider>
)
}
as shown in the code, you are re-creating a new QueryClient inside the App component:
function MyApp({ Component, pageProps }: AppProps) {
const queryClient = new QueryClient()
which means that every time the App re-renders, you throw away the Query Cache (which is stored inside the client).
This is likely what happens when you change the route params. If you cannot create the client outside of MyApp (e.g. because you are using server-side rendering), it is advised to create a stable client:
function MyApp({ Component, pageProps }: AppProps) {
const [queryClient] = React.useState(() => new QueryClient())
this is also shown in the SSR docs.

Is there a better and cleaner method other than getInitialProps

I have a nextJS site that has a custom _app.js file with the following code.
I have dynamic content from a cms that i use getServerSide props from for each of the pages I have.
const MyApp = ({ Component, pageProps }) => {
const { global } = pageProps
if (global == null) {
return <ErrorPage statusCode={404} />
}
if(global.attributes === undefined){
return <Component />
}
const { metadata, favicon, metaTitleSuffix } = global.attributes
return (
<>
{/* Favicon */}
<Head>
<link
rel="shortcut icon"
href={getStrapiMedia(favicon.data.attributes.url)}
/>
</Head>
{/* Global site metadata */}
<DefaultSeo
titleTemplate={`%s | ${metaTitleSuffix}`}
title="Page"
description={metadata.metaDescription}
/>
{/* Display the content */}
<Component {...pageProps} />
</>
)
}
MyApp.getInitialProps = async (appContext) => {
const appProps = await App.getInitialProps(appContext)
const globalLocale = await getGlobalData(appContext.router.locale)
return {
...appProps,
pageProps: {
global: globalLocale,
},
}
}
export default MyApp
Two questions in a way.
A. Do I even need this getInitialProps?
B. Is there a better way of using getInitialProps
Let me know if anymore code is needed

Resources