fix(landing): No hydration issues and fix i18n SSR warning
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
const featureIcons = [
|
||||
'i-lucide-puzzle',
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
const painPointIcons = [
|
||||
'i-lucide-refresh-cw',
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
const painPointIcons = [
|
||||
'i-lucide-search',
|
||||
|
||||
@@ -174,5 +174,5 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// No additional script needed - using $t() from i18n
|
||||
useI18n({ useScope: 'global' })
|
||||
</script>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
const featureIcons = [
|
||||
'i-lucide-route',
|
||||
|
||||
@@ -168,7 +168,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
const isHovered = ref(false)
|
||||
|
||||
const bulletPointCount = 3
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(() =>
|
||||
|
||||
@@ -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') },
|
||||
|
||||
@@ -172,5 +172,5 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Component uses $t() from i18n auto-import
|
||||
useI18n({ useScope: 'global' })
|
||||
</script>
|
||||
|
||||
@@ -79,3 +79,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
useI18n({ useScope: 'global' })
|
||||
</script>
|
||||
@@ -47,6 +47,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
useI18n({ useScope: 'global' })
|
||||
|
||||
defineProps<{
|
||||
compact?: boolean
|
||||
}>()
|
||||
|
||||
@@ -59,6 +59,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
useI18n({ useScope: 'global' })
|
||||
|
||||
defineProps<{
|
||||
compact?: boolean
|
||||
}>()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t, locale } = useI18n()
|
||||
const { t, locale } = useI18n({ useScope: 'global' })
|
||||
|
||||
// SEO Meta
|
||||
useSeoMeta({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t, locale } = useI18n()
|
||||
const { t, locale } = useI18n({ useScope: 'global' })
|
||||
|
||||
// SEO Meta
|
||||
useSeoMeta({
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t, locale } = useI18n()
|
||||
const { t, locale } = useI18n({ useScope: 'global' })
|
||||
|
||||
// SEO Meta
|
||||
useSeoMeta({
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t, locale } = useI18n()
|
||||
const { t, locale } = useI18n({ useScope: 'global' })
|
||||
|
||||
// SEO Meta
|
||||
useSeoMeta({
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user