Skip to content

Commit 1ace3ff

Browse files
committed
fix: hydration mismatch for search and setting page
- fix empty page rendered when search provider is npm in localstorage
1 parent 000933b commit 1ace3ff

File tree

5 files changed

+58
-35
lines changed

5 files changed

+58
-35
lines changed

app/composables/atproto/useAtproto.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ declare global {
1414
var __useAtprotoMock: UseAtprotoReturn | undefined
1515
}
1616

17-
function _useAtprotoImpl(): UseAtprotoReturn {
17+
export const useAtproto = createSharedComposable(function useAtprotoImpl(): UseAtprotoReturn {
1818
if (import.meta.test && globalThis.__useAtprotoMock) {
1919
return globalThis.__useAtprotoMock
2020
}
@@ -37,10 +37,4 @@ function _useAtprotoImpl(): UseAtprotoReturn {
3737
}
3838

3939
return { user, pending, logout }
40-
}
41-
42-
// In tests, skip createSharedComposable so each call checks globalThis.__useAtprotoMock fresh.
43-
// In production, import.meta.test is false and the test branch is tree-shaken.
44-
export const useAtproto = import.meta.test
45-
? _useAtprotoImpl
46-
: createSharedComposable(_useAtprotoImpl)
40+
})

app/composables/npm/useSearch.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,23 @@ export function useSearch(
138138
suggestionsLoading.value = false
139139
}
140140

141+
// Bridge SSR payload when provider differs between server and client.
142+
// SSR always uses the default provider ('algolia'), but the client may
143+
// read a different provider from localStorage. Copy the SSR data to the
144+
// client's cache key so useLazyAsyncData can hydrate without a mismatch.
145+
if (import.meta.client) {
146+
const nuxtApp = useNuxtApp()
147+
const q = toValue(query)
148+
const provider = toValue(searchProvider)
149+
if (nuxtApp.isHydrating && q && provider !== 'algolia') {
150+
const ssrKey = `search:algolia:${q}`
151+
const clientKey = `search:${provider}:${q}`
152+
if (nuxtApp.payload.data[ssrKey] && !nuxtApp.payload.data[clientKey]) {
153+
nuxtApp.payload.data[clientKey] = nuxtApp.payload.data[ssrKey]
154+
}
155+
}
156+
}
157+
141158
const asyncData = useLazyAsyncData(
142159
() => `search:${toValue(searchProvider)}:${toValue(query)}`,
143160
async (_nuxtApp, { signal }) => {

app/composables/useUserPreferencesProvider.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ function createProvider(defaultValue: HydratedUserPreferences) {
4040

4141
async function initSync(): Promise<void> {
4242
if (syncInitialized || import.meta.server) return
43+
const { applyStoredColorMode } = useColorModePreference()
44+
4345
syncInitialized = true
4446

4547
// Resolve auth + sync dependencies lazily
@@ -105,6 +107,8 @@ function createProvider(defaultValue: HydratedUserPreferences) {
105107
await syncWithServer()
106108
}
107109

110+
applyStoredColorMode()
111+
108112
watch(
109113
preferences,
110114
newPrefs => {

app/pages/settings.vue

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -238,26 +238,40 @@ const setLocale: typeof setNuxti18nLocale = newLocale => {
238238
</template>
239239
</ClientOnly>
240240

241-
<!-- Provider description -->
242-
<p class="text-xs text-fg-subtle mt-2">
243-
{{
244-
preferences.searchProvider === 'algolia'
245-
? $t('settings.data_source.algolia_description')
246-
: $t('settings.data_source.npm_description')
247-
}}
248-
</p>
249-
250-
<!-- Algolia attribution -->
251-
<a
252-
v-if="preferences.searchProvider === 'algolia'"
253-
href="https://www.algolia.com/developers"
254-
target="_blank"
255-
rel="noopener noreferrer"
256-
class="inline-flex items-center gap-1 text-xs text-fg-subtle hover:text-fg-muted transition-colors mt-2"
257-
>
258-
{{ $t('search.algolia_disclaimer') }}
259-
<span class="i-lucide:external-link w-3 h-3" aria-hidden="true" />
260-
</a>
241+
<!-- Provider description & Algolia attribution -->
242+
<ClientOnly>
243+
<p class="text-xs text-fg-subtle mt-2">
244+
{{
245+
preferences.searchProvider === 'algolia'
246+
? $t('settings.data_source.algolia_description')
247+
: $t('settings.data_source.npm_description')
248+
}}
249+
</p>
250+
<a
251+
v-if="preferences.searchProvider === 'algolia'"
252+
href="https://www.algolia.com/developers"
253+
target="_blank"
254+
rel="noopener noreferrer"
255+
class="inline-flex items-center gap-1 text-xs text-fg-subtle hover:text-fg-muted transition-colors mt-2"
256+
>
257+
{{ $t('search.algolia_disclaimer') }}
258+
<span class="i-lucide:external-link w-3 h-3" aria-hidden="true" />
259+
</a>
260+
<template #fallback>
261+
<p class="text-xs text-fg-subtle mt-2">
262+
{{ $t('settings.data_source.algolia_description') }}
263+
</p>
264+
<a
265+
href="https://www.algolia.com/developers"
266+
target="_blank"
267+
rel="noopener noreferrer"
268+
class="inline-flex items-center gap-1 text-xs text-fg-subtle hover:text-fg-muted transition-colors mt-2"
269+
>
270+
{{ $t('search.algolia_disclaimer') }}
271+
<span class="i-lucide:external-link w-3 h-3" aria-hidden="true" />
272+
</a>
273+
</template>
274+
</ClientOnly>
261275
</div>
262276

263277
<div class="border-t border-border my-4" />
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
export default defineNuxtPlugin({
22
name: 'preferences-sync',
33
setup() {
4-
const { initSync } = useInitUserPreferencesSync()
5-
const { applyStoredColorMode } = useColorModePreference()
6-
7-
// Apply stored color mode preference early (before components mount)
8-
applyStoredColorMode()
9-
104
// Initialize server sync for authenticated users
11-
initSync()
5+
void useInitUserPreferencesSync().initSync()
126
},
137
})

0 commit comments

Comments
 (0)