#Hydration issue with docker

1 messages · Page 1 of 1 (latest)

lilac hemlock
#

I have app running on a server with docker.
Cause of infrastructure and setup of a server and reverse proxy to a server I can't call api endpoint using domain for server side calls. So I have to make a request using a container hostname so request on server side never leave server. Issue with that that I get hydration errors since client side is calling api via domain name while server side has to call it using docker container hostname.

Is there a way to make api call using 2 different domains to the same endpoint and not have hydration missmatch error?

winged oasisBOT
#

Hi @lilac hemlock. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

You’re running into a classic “different data between server and client” hydration problem, but in your case the data is the URL itself (hostname differs between SSR and browser).

From the docs, Nuxt warns that any difference between server-rendered content and client-rendered content will cause hydration mismatch, and recommends using SSR‑friendly composables (useFetch, useAsyncData, useState) so that the same data is reused on the client instead of being recomputed differently. [Hydration reasons; Data fetching]

Also, calling $fetch directly in setup will always refetch on the client, which is exactly what you don’t want here. [$fetch docs]

Given the constraints (different hostnames inside Docker vs in browser), the pattern that is documented is:

  1. Use a single logical URL in your components (e.g. /api/foo or '/my-endpoint'), and
  2. Resolve the actual base URL differently on server vs client, but only in one place, so the data returned is the same.

The Strapi/Docker answer shows exactly this: they compute a different base URL depending on process.server, but the component itself just calls one function, so the response shape is identical on both sides. [Strapi Docker answer]

Example adapted to your case:

// composables/useApi.ts
export async function useApi<T>(path: string, options?: RequestInit): Promise<T> {
  const runtimeConfig = useRuntimeConfig()
#

// Choose base URL depending on environment
const baseURL = process.server
? 'http://your-docker-service:3000' // internal hostname in Docker network
: runtimeConfig.public.apiBase // public domain, e.g. https://example.com

return await $fetch<T>(baseURL + path, options)
}


Then in your page/component, **wrap it with `useFetch` or `useAsyncData`** so the server result is reused on the client and not refetched with a different hostname:

```vue
<script setup lang="ts">
const { data, error } = await useAsyncData('my-endpoint', () =>
  useApi('/api/my-endpoint')
)
</script>

<template>
  <div v-if="data">
    {{ data }}
  </div>
</template>

Because useAsyncData forwards the server-fetched payload to the client, the client won’t make a second call with a different base URL, so there’s no hydration mismatch. [Data fetching; $fetch docs]

If you must call directly from the browser (e.g. form submissions), that’s fine: those calls happen after hydration and won’t affect the initial SSR markup.

If you still see mismatches, check that:

  • The response body is identical regardless of which hostname is used.
  • You’re not using raw $fetch in setup without useFetch/useAsyncData, which would cause double fetching and potential divergence. [$fetch docs]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@lilac hemlock Kapa.ai is still learning and improving, please let me know how I did by reacting below.

lilac hemlock
#

@winged oasis what about middlewares? I have middlewares that check if user is authenticated, but I have the same issue.

winged oasisBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Nuxt middleware runs on both server and client, so you can hit the same “different base URL in Docker vs browser” problem there as well. [Nuxt lifecycle]

From the docs, the key points that still apply in middleware:

  • Use SSR‑friendly fetch (useFetch, useAsyncData, or a custom wrapper) so the same data is reused between server and client. [Nuxt lifecycle; $fetch docs]
  • If you need different behavior on server vs client, you can branch with import.meta.server / import.meta.client. [Nuxt lifecycle]

For auth checks in middleware, you can apply the same pattern as with pages:

  1. Centralize the base URL logic (Docker hostname vs public domain) in one place (e.g. a custom $fetch or composable). [Custom useFetch]
  2. In middleware, always call that wrapper, not raw $fetch with hard‑coded hostnames.

Example pattern (based on the documented custom $fetch recipe):

// app/plugins/api.ts
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()

  const api = $fetch.create({
    baseURL: import.meta.server
      ? '<http://your-docker-service:3000'> // internal hostname
      : config.public.apiBase,            // public domain
  })

  return {
    provide: { api },
  }
})

Then in middleware:

// app/middleware/auth.global.ts
export default defineNuxtRouteMiddleware(async () => {
  const { $api } = useNuxtApp()

  // Same call on server and client, baseURL differs internally
  const user = await $api('/auth/me')
#

if (!user) {
return navigateTo('/login')
}
})


Because the hostname difference is hidden inside the plugin, the **request path and response data are the same** for server and client, which avoids hydration mismatches while still letting the server talk to the Docker hostname. [[Custom useFetch](<https://nuxt.com/docs/4.x/guide/recipes/custom-usefetch#custom-fetch>)]
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------