Reviewed-on: #433 Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #433 でマージされました。
このコミットが含まれているのは:
+112
-10
@@ -1,11 +1,17 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { QueryClient, QueryClientProvider, hydrate } from '@tanstack/react-query'
|
||||
import { createRoot, hydrateRoot } from 'react-dom/client'
|
||||
import { HelmetProvider } from 'react-helmet-async'
|
||||
import { I18nextProvider } from 'react-i18next'
|
||||
|
||||
import '@/index.css'
|
||||
import App from '@/App'
|
||||
import App, { preloadPageRoute, resolvePageRoute } from '@/App'
|
||||
import { createUII18n } from '@/lib/i18n'
|
||||
import { currentViewingLocale } from '@/lib/locales'
|
||||
import { localesKeys } from '@/lib/queryKeys'
|
||||
import { applyClientAnimationMode, getClientAnimationMode } from '@/lib/settings'
|
||||
|
||||
import type { DehydratedState } from '@tanstack/react-query'
|
||||
|
||||
const helmetContext = { }
|
||||
|
||||
const client = new QueryClient ({
|
||||
@@ -14,11 +20,107 @@ const client = new QueryClient ({
|
||||
gcTime: 30 * 60 * 1000,
|
||||
retry: 1 } } })
|
||||
|
||||
applyClientAnimationMode (getClientAnimationMode ())
|
||||
|
||||
createRoot (document.getElementById ('root')!).render (
|
||||
<HelmetProvider context={helmetContext}>
|
||||
<QueryClientProvider client={client}>
|
||||
<App/>
|
||||
</QueryClientProvider>
|
||||
</HelmetProvider>)
|
||||
const mount = (serverRendered: boolean) => {
|
||||
const instance = createUII18n (currentViewingLocale ())
|
||||
const app = (
|
||||
<HelmetProvider context={helmetContext}>
|
||||
<QueryClientProvider client={client}>
|
||||
<I18nextProvider i18n={instance}>
|
||||
<App/>
|
||||
</I18nextProvider>
|
||||
</QueryClientProvider>
|
||||
</HelmetProvider>)
|
||||
const root = document.getElementById ('root')!
|
||||
if (serverRendered)
|
||||
hydrateRoot (root, app)
|
||||
else
|
||||
createRoot (root).render (app)
|
||||
}
|
||||
|
||||
|
||||
const start = () => {
|
||||
applyClientAnimationMode (getClientAnimationMode ())
|
||||
const bootstrap = document.getElementById ('ssr-state')
|
||||
if (bootstrap == null)
|
||||
{
|
||||
mount (false)
|
||||
return
|
||||
}
|
||||
const storageKey = 'btrc_hub.ssr-recovery'
|
||||
const identity = `${import.meta.url}:${location.pathname}${location.search}`
|
||||
let pending = false
|
||||
let mounted = false
|
||||
const retry = () => { void attempt () }
|
||||
const attempt = async () => {
|
||||
if (pending || mounted)
|
||||
return
|
||||
pending = true
|
||||
try
|
||||
{
|
||||
const data = JSON.parse (bootstrap.textContent ?? '') as {
|
||||
state: DehydratedState; status: number }
|
||||
if (data?.state == null || !(Array.isArray (data.state.queries)))
|
||||
throw new Error ('Invalid SSR query state')
|
||||
hydrate (client, data.state)
|
||||
for (const query of data.state.queries)
|
||||
{
|
||||
if (query.state.status === 'error')
|
||||
client.setQueryDefaults (query.queryKey, { retryOnMount: false })
|
||||
}
|
||||
if (client.getQueryData (localesKeys.index) == null)
|
||||
throw new Error ('Missing SSR locales')
|
||||
const url = new URL (window.location.href)
|
||||
const match = resolvePageRoute (url.pathname)
|
||||
const prepare = match?.route.handle?.prepare
|
||||
if (prepare != null && match != null)
|
||||
{
|
||||
const status = await prepare ({ client, params: match.params,
|
||||
locale: currentViewingLocale (), searchParams: url.searchParams })
|
||||
if (status !== data.status)
|
||||
throw new Error ('Server and browser page versions differ')
|
||||
}
|
||||
await preloadPageRoute (url.pathname, currentViewingLocale ())
|
||||
mount (true)
|
||||
mounted = true
|
||||
for (const event of ['online', 'focus', 'pageshow'])
|
||||
window.removeEventListener (event, retry)
|
||||
try
|
||||
{
|
||||
sessionStorage.removeItem (storageKey)
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Storage may be unavailable; successful hydration needs no recovery marker.
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error ('[SSR] Preparation failed; keeping server HTML', error)
|
||||
// A new document also clears failed dynamic-import module caches.
|
||||
// Never clear the root or attempt CSR over unmatched server markup.
|
||||
try
|
||||
{
|
||||
if (navigator.onLine && sessionStorage.getItem (storageKey) !== identity)
|
||||
{
|
||||
sessionStorage.setItem (storageKey, identity)
|
||||
window.location.reload ()
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Keep the readable document and retry on online/focus/pageshow instead.
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
pending = false
|
||||
}
|
||||
}
|
||||
for (const event of ['online', 'focus', 'pageshow'])
|
||||
window.addEventListener (event, retry)
|
||||
retry ()
|
||||
}
|
||||
|
||||
|
||||
start ()
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする