fix(landing): No hydration issues and fix i18n SSR warning

This commit is contained in:
2026-01-16 17:09:42 +01:00
parent 9cfa5ec434
commit 75f12ce775
22 changed files with 40 additions and 39 deletions

View File

@@ -18,9 +18,9 @@
}"
>
<template #title>
<NuxtLink to="/" class="flex items-center gap-2 sm:gap-3 group">
<div class="flex items-center gap-2 sm:gap-3">
<div
class="w-10 h-10 rounded-xl bg-linear-to-br from-primary-500 to-cyan-500 flex items-center justify-center shadow-lg shadow-primary-500/25 group-hover:shadow-primary-500/40 transition-shadow"
class="w-10 h-10 rounded-xl bg-linear-to-br from-primary-500 to-cyan-500 flex items-center justify-center shadow-lg shadow-primary-500/25"
>
<UIcon name="i-lucide-scale" class="w-5 h-5 text-white" />
</div>
@@ -29,7 +29,7 @@
>
GremiumHub
</span>
</NuxtLink>
</div>
</template>
<UNavigationMenu
@@ -183,7 +183,7 @@
<script setup lang="ts">
import type { NavigationMenuItem, DropdownMenuItem } from '@nuxt/ui'
const { t, locale, locales, setLocale } = useI18n()
const { t, locale, locales, setLocale } = useI18n({ useScope: 'global' })
// Locale dropdown menu items
const localeMenuItems = computed<DropdownMenuItem[]>(() =>
@@ -198,7 +198,8 @@ const localeMenuItems = computed<DropdownMenuItem[]>(() =>
)
// Track scroll position for header styling
const isScrolled = ref(false)
// Use useState to ensure consistent SSR/client hydration
const isScrolled = useState('header-scrolled', () => false)
onMounted(() => {
const handleScroll = () => {
@@ -206,7 +207,10 @@ onMounted(() => {
}
window.addEventListener('scroll', handleScroll, { passive: true })
handleScroll() // Check initial position
// Defer initial scroll check to avoid hydration mismatch
nextTick(() => {
handleScroll()
})
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)

View File

@@ -84,7 +84,7 @@
</template>
<script setup lang="ts">
const { t } = useI18n()
const { t } = useI18n({ useScope: 'global' })
const featureIcons = [
'i-lucide-puzzle',

View File

@@ -130,7 +130,7 @@
</template>
<script setup lang="ts">
const { t } = useI18n()
const { t } = useI18n({ useScope: 'global' })
const painPointIcons = [
'i-lucide-refresh-cw',

View File

@@ -130,7 +130,7 @@
</template>
<script setup lang="ts">
const { t } = useI18n()
const { t } = useI18n({ useScope: 'global' })
const painPointIcons = [
'i-lucide-search',

View File

@@ -174,5 +174,5 @@
</template>
<script setup lang="ts">
// No additional script needed - using $t() from i18n
useI18n({ useScope: 'global' })
</script>

View File

@@ -45,7 +45,7 @@
</template>
<script setup lang="ts">
const { t } = useI18n()
const { t } = useI18n({ useScope: 'global' })
const featureIcons = [
'i-lucide-route',

View File

@@ -168,7 +168,7 @@
</template>
<script setup lang="ts">
const { t } = useI18n()
const { t } = useI18n({ useScope: 'global' })
const isHovered = ref(false)
const bulletPointCount = 3

View File

@@ -130,24 +130,9 @@
</div>
</div>
<!-- Scroll indicator - stable wrapper for click target, animated inner element -->
<div class="absolute bottom-8 left-1/2 -translate-x-1/2 z-20">
<a href="#betriebsraete" class="block" :aria-label="$t('hero.scrollDown')" @click.prevent="scrollToSection">
<div
class="animate-bounce flex items-center justify-center w-12 h-12 rounded-full glass text-gray-600 dark:text-gray-300 hover:bg-white/80 dark:hover:bg-gray-800/80 transition-colors"
>
<UIcon name="i-lucide-chevron-down" class="w-6 h-6" />
</div>
</a>
</div>
</section>
</template>
<script setup lang="ts">
const scrollToSection = () => {
const target = document.querySelector('#betriebsraete')
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
useI18n({ useScope: 'global' })
</script>

View File

@@ -126,7 +126,7 @@
class="mt-6 text-sm text-center text-gray-500 dark:text-gray-400 animate-fade-in-up"
style="animation-delay: 300ms"
>
<i18n-t keypath="newsletter.privacyNote" tag="span">
<i18n-t keypath="newsletter.privacyNote" tag="span" scope="global">
<template #link>
<NuxtLink to="/datenschutz" class="text-primary-600 dark:text-primary-400 hover:underline font-medium">
{{ $t('newsletter.privacyLink') }}
@@ -169,7 +169,7 @@
import { z } from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const { t } = useI18n()
const { t } = useI18n({ useScope: 'global' })
const { isLoading, isSuccess, submitEmail } = useNewsletterSignup()
const schema = computed(() =>

View File

@@ -52,7 +52,7 @@
</template>
<script setup lang="ts">
const { t } = useI18n()
const { t } = useI18n({ useScope: 'global' })
const stats = computed(() => [
{ value: 70, suffix: '%', label: t('stats.timeSaved') },

View File

@@ -172,5 +172,5 @@
</template>
<script setup lang="ts">
// Component uses $t() from i18n auto-import
useI18n({ useScope: 'global' })
</script>

View File

@@ -79,3 +79,6 @@
</div>
</template>
<script setup lang="ts">
useI18n({ useScope: 'global' })
</script>

View File

@@ -47,6 +47,8 @@
</template>
<script setup lang="ts">
useI18n({ useScope: 'global' })
defineProps<{
compact?: boolean
}>()

View File

@@ -59,6 +59,8 @@
</template>
<script setup lang="ts">
useI18n({ useScope: 'global' })
defineProps<{
compact?: boolean
}>()

View File

@@ -5,12 +5,12 @@ export interface ContactFormData {
}
export function useContactForm() {
const { t } = useI18n()
const isLoading = ref(false)
const isSuccess = ref(false)
const error = ref<string | null>(null)
const submitForm = async (data: ContactFormData) => {
const { t } = useI18n({ useScope: 'global' })
isLoading.value = true
error.value = null

View File

@@ -1,10 +1,10 @@
export function useNewsletterSignup() {
const { t } = useI18n()
const isLoading = ref(false)
const isSuccess = ref(false)
const error = ref<string | null>(null)
const submitEmail = async (email: string) => {
const { t } = useI18n({ useScope: 'global' })
isLoading.value = true
error.value = null

View File

@@ -27,7 +27,7 @@
</template>
<script setup lang="ts">
const { t, locale } = useI18n()
const { t, locale } = useI18n({ useScope: 'global' })
// SEO Meta
useSeoMeta({

View File

@@ -194,7 +194,7 @@ import { z } from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
import type { ContactFormData } from '~/composables/useContactForm'
const { t, locale } = useI18n()
const { t, locale } = useI18n({ useScope: 'global' })
const { isLoading, isSuccess, error, submitForm, reset } = useContactForm()
// SEO Meta

View File

@@ -76,7 +76,7 @@
</template>
<script setup lang="ts">
const { t, locale } = useI18n()
const { t, locale } = useI18n({ useScope: 'global' })
// SEO Meta
useSeoMeta({

View File

@@ -3,7 +3,7 @@
</template>
<script setup lang="ts">
const { t, locale } = useI18n()
const { t, locale } = useI18n({ useScope: 'global' })
// SEO Meta
useSeoMeta({

View File

@@ -5,7 +5,7 @@
</template>
<script setup lang="ts">
const { t, locale } = useI18n()
const { t, locale } = useI18n({ useScope: 'global' })
// SEO Meta
useSeoMeta({

View File

@@ -5,6 +5,11 @@ export default defineNuxtConfig({
devtools: { enabled: true },
ssr: true,
// Icon configuration - bundle icons for SSR to prevent hydration mismatches
icon: {
serverBundle: 'remote'
},
// Runtime configuration for server-side API keys
runtimeConfig: {
brevoApiKey: 'NOT_SET',