Skip to content

Commit

Permalink
555 - Server-side query prefetching (SEO, no loadings) (#557)
Browse files Browse the repository at this point in the history
* simplify/unify `apiAxios`

* refactor: simplify sharing `getServerSideProps` between seminar subpages

* #555 PoC - prefetch Posts on server

* #555 - prefetch flat pages on server

* #555 - prefetch competitions on server

* #555 - prefetch menu and footer queries

* #555 - commonQueries on all sites, rework reset-password params

* #555 - queryClient `staleTime` - don't refetch on client immediately

* fix `myprofile` call and use new `apiOptions`
  • Loading branch information
rtrembecky authored Dec 25, 2024
1 parent 4a2b433 commit 38cef03
Show file tree
Hide file tree
Showing 49 changed files with 473 additions and 210 deletions.
55 changes: 55 additions & 0 deletions src/api/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {ILogo} from '@/components/PageLayout/Footer/Logo'
import {IPost} from '@/components/Posts/Post'
import {FlatPage} from '@/types/api/base'
import {MenuItemShort} from '@/types/api/cms'
import {Competition, Event} from '@/types/api/competition'
import {Profile} from '@/types/api/personal'
import {SeminarId} from '@/utils/useSeminarInfo'

import {apiAxios} from './apiAxios'

type OurCompetition = Omit<Competition, 'history_events'> & {history_events: Event[]}

export const apiOptions = {
cms: {
flatPage: {
byUrl: (pageUrl: string) => ({
queryKey: ['cms', 'flat-page', 'by-url', pageUrl],
queryFn: () => apiAxios.get<FlatPage>(`/cms/flat-page/by-url/${pageUrl}`).then((res) => res.data),
}),
},
logo: () => ({
queryKey: ['cms', 'logo'],
queryFn: () => apiAxios.get<ILogo[]>('/cms/logo').then((res) => res.data),
}),
menuItem: {
onSite: (seminarId: SeminarId, type: 'menu' | 'footer') => ({
queryKey: ['cms', 'menu-item', 'on-site', seminarId, type],
queryFn: () =>
apiAxios.get<MenuItemShort[]>(`/cms/menu-item/on-site/${seminarId}?type=${type}`).then((res) => res.data),
}),
},
post: {
visible: (seminarId: SeminarId) => ({
queryKey: ['cms', 'post', 'visible', seminarId],
queryFn: () => apiAxios.get<IPost[]>(`/cms/post/visible?sites=${seminarId}`).then((res) => res.data),
}),
},
},
competition: {
competition: {
slug: (slug: string) => ({
queryKey: ['competition', 'competition', 'slug', slug],
queryFn: () => apiAxios.get<OurCompetition>(`/competition/competition/slug/${slug}`).then((res) => res.data),
}),
},
},
personal: {
profiles: {
myprofile: () => ({
queryKey: ['personal', 'profiles', 'myprofile'],
queryFn: () => apiAxios.get<Profile>('/personal/profiles/myprofile').then((res) => res.data),
}),
},
},
}
12 changes: 6 additions & 6 deletions src/api/apiAxios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import {apiInterceptor} from '@/api/apiInterceptor'
import {debugServer} from '@/utils/debugServer'
import {getBackendServerUrl} from '@/utils/urlBase'

export const newApiAxios = (base: 'server' | 'client') => {
export const newApiAxios = () => {
const isServer = typeof window === 'undefined'

const instance = axios.create({
// axios requesty mozu byt z FE next serveru alebo z browsru.
// - server vola BE URL (podla env vars) priamo
// - browser vola lokalnu /api URL
// - na deployed verzii (test.strom.sk, strom.sk) to chyti nginx a posle na deployed BE
// - na localhoste to chyti next middleware, kde to rewritneme na BE URL (podla env vars)
baseURL: base === 'server' ? `${getBackendServerUrl()}/api` : '/api',
baseURL: isServer ? `${getBackendServerUrl()}/api` : '/api',
// auth pozostava z comba:
// 1. `sessionid` httpOnly cookie - nastavuje a maze su server pri login/logout
// 2. CSRF hlavicka - server nastavuje cookie, ktorej hodnotu treba vlozit do hlavicky. axios riesi automaticky podla tohto configu
Expand All @@ -22,7 +24,7 @@ export const newApiAxios = (base: 'server' | 'client') => {
withCredentials: true,
})

if (base === 'server') {
if (isServer) {
// prvy definovany interceptor bezi posledny. logujeme finalnu URL
instance.interceptors.request.use((config) => {
const {method, url, baseURL} = config
Expand All @@ -44,6 +46,4 @@ export const newApiAxios = (base: 'server' | 'client') => {
}

// nasa "globalna" API instancia
export const apiAxios = newApiAxios('client')

export const serverApiAxios = newApiAxios('server')
export const apiAxios = newApiAxios()
15 changes: 15 additions & 0 deletions src/api/commonQueries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {QueryClient} from '@tanstack/react-query'

import {getSeminarInfoFromPathname} from '@/utils/useSeminarInfo'

import {apiOptions} from './api'

export const commonQueries = (queryClient: QueryClient, resolvedUrl: string) => {
const {seminarId} = getSeminarInfoFromPathname(resolvedUrl)

return [
queryClient.prefetchQuery(apiOptions.cms.menuItem.onSite(seminarId, 'menu')),
queryClient.prefetchQuery(apiOptions.cms.menuItem.onSite(seminarId, 'footer')),
queryClient.prefetchQuery(apiOptions.cms.logo()),
]
}
23 changes: 6 additions & 17 deletions src/components/PageLayout/Footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import Grid from '@mui/material/Unstable_Grid2'
import {useQuery} from '@tanstack/react-query'
import {FC} from 'react'

import {apiAxios} from '@/api/apiAxios'
import {apiOptions} from '@/api/api'
import {Link} from '@/components/Clickable/Link'
import {Loading} from '@/components/Loading/Loading'
import {ILogo, Logo} from '@/components/PageLayout/Footer/Logo'
import {MenuItemShort} from '@/types/api/cms'
import {Logo} from '@/components/PageLayout/Footer/Logo'
import {useSeminarInfo} from '@/utils/useSeminarInfo'

export const Footer: FC = () => {
Expand All @@ -17,21 +16,11 @@ export const Footer: FC = () => {
data: menuItemsData,
isLoading: menuItemsIsLoading,
error: menuItemsError,
} = useQuery({
queryKey: ['cms', 'menu-item', 'on-site', seminarId, '?footer'],
queryFn: () => apiAxios.get<MenuItemShort[]>(`/cms/menu-item/on-site/${seminarId}?type=footer`),
})
const menuItems = menuItemsData?.data ?? []
} = useQuery(apiOptions.cms.menuItem.onSite(seminarId, 'footer'))
const menuItems = menuItemsData ?? []

const {
data: logosData,
isLoading: logosIsLoading,
error: logosError,
} = useQuery({
queryKey: ['cms', 'logo'],
queryFn: () => apiAxios.get<ILogo[]>('/cms/logo'),
})
const logos = logosData?.data.filter((logo) => !logo.disabled) ?? []
const {data: logosData, isLoading: logosIsLoading, error: logosError} = useQuery(apiOptions.cms.logo())
const logos = logosData?.filter((logo) => !logo.disabled) ?? []

return (
<Grid
Expand Down
12 changes: 5 additions & 7 deletions src/components/PageLayout/MenuMain/MenuMain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import {useQuery} from '@tanstack/react-query'
import {useRouter} from 'next/router'
import {FC, useEffect, useState} from 'react'

import {apiAxios} from '@/api/apiAxios'
import {apiOptions} from '@/api/api'
import {Link} from '@/components/Clickable/Link'
import {CloseButton} from '@/components/CloseButton/CloseButton'
import {Loading} from '@/components/Loading/Loading'
import Menu from '@/svg/menu.svg'
import {MenuItemShort} from '@/types/api/cms'
import {useHasPermissions} from '@/utils/useHasPermissions'
import {useSeminarInfo} from '@/utils/useSeminarInfo'

Expand Down Expand Up @@ -41,11 +40,10 @@ export const MenuMain: FC = () => {
}
}, [router, fullWidthMenu])

const {data: menuItemsData, isLoading: menuItemsIsLoading} = useQuery({
queryKey: ['cms', 'menu-item', 'on-site', seminarId, '?menu'],
queryFn: () => apiAxios.get<MenuItemShort[]>(`/cms/menu-item/on-site/${seminarId}?type=menu`),
})
const menuItems = menuItemsData?.data ?? []
const {data: menuItemsData, isLoading: menuItemsIsLoading} = useQuery(
apiOptions.cms.menuItem.onSite(seminarId, 'menu'),
)
const menuItems = menuItemsData ?? []

const lg = useMediaQuery<Theme>((theme) => theme.breakpoints.up('lg'))
const iconSize = lg ? 34 : 24
Expand Down
15 changes: 6 additions & 9 deletions src/components/Posts/Posts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,21 @@ import {Stack, Typography} from '@mui/material'
import {useQuery} from '@tanstack/react-query'
import {FC} from 'react'

import {apiAxios} from '@/api/apiAxios'
import {apiOptions} from '@/api/api'
import {useSeminarInfo} from '@/utils/useSeminarInfo'

import {Loading} from '../Loading/Loading'
import {IPost, Post} from './Post'
import {Post} from './Post'

export const Posts: FC = () => {
const {seminarId} = useSeminarInfo()

const {
data: postsData,
isLoading: postsIsLoading,
error: postsError,
} = useQuery({
queryKey: ['cms', 'post', 'visible'],
queryFn: () => apiAxios.get<IPost[]>(`/cms/post/visible?sites=${seminarId}`),
})
const posts = postsData?.data ?? []

const {seminarId} = useSeminarInfo()
} = useQuery(apiOptions.cms.post.visible(seminarId))
const posts = postsData ?? []

if (postsIsLoading) return <Loading />

Expand Down
32 changes: 19 additions & 13 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {CssBaseline} from '@mui/material'
import {ThemeProvider} from '@mui/material/styles'
import {QueryClient, QueryClientProvider} from '@tanstack/react-query'
import {HydrationBoundary, QueryClient, QueryClientProvider} from '@tanstack/react-query'
import {ReactQueryDevtools} from '@tanstack/react-query-devtools'
import {isAxiosError} from 'axios'
import {AppProps} from 'next/app'
Expand Down Expand Up @@ -33,6 +33,10 @@ const ReactQueryProvider: FC<PropsWithChildren> = ({children}) => {
if (failureCount >= 3) return false
return true
},
// https://tanstack.com/query/latest/docs/framework/react/guides/ssr#initial-setup
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
mutations: {
// globalny error handler requestov cez useMutation
Expand Down Expand Up @@ -89,18 +93,20 @@ const MyApp: FC<AppProps> = ({Component, pageProps}) => {
</Head>
<AlertContainer.Provider>
<ReactQueryProvider>
<ReactQueryDevtools />
<CookiesProvider>
<AuthContainer.Provider>
<BannerAnimationContainer.Provider>
<ThemeProvider theme={theme}>
<CssBaseline />
<AlertBox />
<Component {...pageProps} />
</ThemeProvider>
</BannerAnimationContainer.Provider>
</AuthContainer.Provider>
</CookiesProvider>
<HydrationBoundary state={pageProps.dehydratedState}>
<ReactQueryDevtools />
<CookiesProvider>
<AuthContainer.Provider>
<BannerAnimationContainer.Provider>
<ThemeProvider theme={theme}>
<CssBaseline />
<AlertBox />
<Component {...pageProps} />
</ThemeProvider>
</BannerAnimationContainer.Provider>
</AuthContainer.Provider>
</CookiesProvider>
</HydrationBoundary>
</ReactQueryProvider>
</AlertContainer.Provider>
</>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/malynar/[page].tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import StromStaticPage, {seminarBasedGetServerSideProps} from '../strom/[page]'
import StromStaticPage, {getServerSideProps} from '../strom/[page]'

export default StromStaticPage

export const getServerSideProps = seminarBasedGetServerSideProps('malynar')
export {getServerSideProps}
4 changes: 3 additions & 1 deletion src/pages/malynar/admin/opravit-ulohu/[[...params]].tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {NextPage} from 'next'

import Page from '../../../strom/admin/opravit-ulohu/[[...params]]'
import Page, {getServerSideProps} from '../../../strom/admin/opravit-ulohu/[[...params]]'

const ProblemAdministration: NextPage = () => {
return <Page />
}

export default ProblemAdministration

export {getServerSideProps}
4 changes: 3 additions & 1 deletion src/pages/malynar/admin/opravovanie/[[...params]].tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {NextPage} from 'next'

import Page from '../../../strom/admin/opravovanie/[[...params]]'
import Page, {getServerSideProps} from '../../../strom/admin/opravovanie/[[...params]]'

const SemesterAdmnistration: NextPage = () => {
return <Page />
}

export default SemesterAdmnistration

export {getServerSideProps}
4 changes: 2 additions & 2 deletions src/pages/malynar/akcie/[[...params]].tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import StaticPage, {competitionBasedGetServerSideProps} from '../../strom/akcie/[[...params]]'
import StaticPage, {getServerSideProps} from '../../strom/akcie/[[...params]]'

export default StaticPage

export const getServerSideProps = competitionBasedGetServerSideProps('malynar')
export {getServerSideProps}
4 changes: 3 additions & 1 deletion src/pages/malynar/archiv/[[...params]].tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {NextPage} from 'next'

import Page from '../../strom/archiv/[[...params]]'
import Page, {getServerSideProps} from '../../strom/archiv/[[...params]]'

const Archiv: NextPage = () => {
return <Page />
}

export default Archiv

export {getServerSideProps}
8 changes: 5 additions & 3 deletions src/pages/malynar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {NextPage} from 'next'

import Page from '../strom/index'
import Page, {getServerSideProps} from '../strom/index'

const Malynar: NextPage = () => {
const Home: NextPage = () => {
return <Page />
}

export default Malynar
export default Home

export {getServerSideProps}
4 changes: 3 additions & 1 deletion src/pages/malynar/profil/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {NextPage} from 'next'

import Page from '../../strom/profil/index'
import Page, {getServerSideProps} from '../../strom/profil/index'

const Profile: NextPage = () => {
return <Page />
}

export default Profile

export {getServerSideProps}
4 changes: 3 additions & 1 deletion src/pages/malynar/profil/uprava.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {NextPage} from 'next'

import Page from '../../strom/profil/uprava'
import Page, {getServerSideProps} from '../../strom/profil/uprava'

const Profil: NextPage = () => {
return <Page />
}

export default Profil

export {getServerSideProps}
4 changes: 3 additions & 1 deletion src/pages/malynar/registracia.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {NextPage} from 'next'

import Page from '../strom/registracia'
import Page, {getServerSideProps} from '../strom/registracia'

const Register: NextPage = () => {
return <Page />
}

export default Register

export {getServerSideProps}
4 changes: 2 additions & 2 deletions src/pages/malynar/reset-password/[...resetToken].tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PasswordReset from '../../strom/reset-password/[...resetToken]'
import PasswordReset, {getServerSideProps} from '../../strom/reset-password/[...resetToken]'

export default PasswordReset

export {getServerSideProps} from '../../strom/reset-password/[...resetToken]'
export {getServerSideProps}
4 changes: 3 additions & 1 deletion src/pages/malynar/verify-email/[verificationKey].tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {NextPage} from 'next'

import Page from '../../strom/verify-email/[verificationKey]'
import Page, {getServerSideProps} from '../../strom/verify-email/[verificationKey]'

const Verify: NextPage = () => {
return <Page />
}

export default Verify

export {getServerSideProps}
Loading

0 comments on commit 38cef03

Please sign in to comment.