Skip to content

Instantly share code, notes, and snippets.

@vojtaholik
Created July 29, 2025 11:51
Show Gist options
  • Save vojtaholik/945d1f007f3b8f9c302ebf3e89f6b3c0 to your computer and use it in GitHub Desktop.
Save vojtaholik/945d1f007f3b8f9c302ebf3e89f6b3c0 to your computer and use it in GitHub Desktop.

Fuck yeah, Nuxt makes perfect sense for your use case! Since you’re planning Vue integration later, this gives you a smooth migration path from static to dynamic. Here’s how your current setup would look in Nuxt:

Nuxt Configuration

// nuxt.config.ts
export default defineNuxtConfig({
  devtools: { enabled: true },
  
  // Generate static files
  nitro: {
    prerender: {
      routes: [/]
    }
  },
  
  // CSS
  css: [~/assets/scss/main.scss’],
  
  // SEO & Meta
  app: {
    head: {
      title: ‘Rim’,
      meta: [
        { name: ‘description’, content: ‘Rim - kancelářský nábytek’ },
        { name: ‘viewport’, content: ‘width=device-width, initial-scale=1 }
      ],
      link: [
        { rel: ‘icon’, type: ‘image/x-icon’, href: /favicon.ico’ }
      ]
    }
  },
  
  // Auto-import components
  components: true
})

Layout Structure

<!-- layouts/default.vue -->
<template>
  <div class=“wrapper” :style=“wrapperStyles”>
    <AppHeader />
    <div class=“content-wrapper” id=“content”>
      <slot />
    </div>
    <AppFooter />
  </div>
</template>

<script setup>
// Dynamic wrapper styles based on page
const route = useRoute()

const wrapperStyles = computed(() => {
  const styles = {}
  
  if (route.name === ‘inspiration-detail’) {
    styles[‘--accent-color’] = ‘#60BABA
  } else if (route.name === ‘designer-detail’) {
    styles[‘--accent-color’] = ‘#AD7267
  }
  
  return styles
})
</script>

Your Index Page

<!-- pages/index.vue -->
<template>
  <div>
    <!-- Intro Slider -->
    <section class=“intro-secondary”>
      <div class=“intro-secondary-slider”>
        <div class=“intro-secondary-slider__slides owl-carousel”>
          <div 
            v-for=“slide in heroSlides” 
            :key=“slide.id”
            class=“intro-secondary-slider-slide”
            :class=“slide.modifierClass”
          >
            <div class=“intro-secondary-slider-slide__img”>
              <picture v-if=“slide.type === ‘image’” class=“slider-image”>
                <source :srcset=“slide.mobileSrc” media=“(max-width: 768px)“>
                <img :src=“slide.src” alt=“”>
              </picture>
              <video 
                v-else
                :poster=“slide.poster” 
                preload autoplay loop muted playsinline
              >
                <source type=“video/mp4" :src=“slide.src”>
              </video>
            </div>
            <div class=“intro-secondary-slider-slide__inner”>
              <div class=“container”>
                <div class=“intro-secondary-slider-slide__content”>
                  <h1 class=“intro-secondary-slider-slide__title”>
                    <NuxtLink :to=“slide.link”>{{ slide.title }}</NuxtLink>
                  </h1>
                  <div class=“intro-secondary-slider-slide__perex apply-formatting”>
                    <p>{{ slide.excerpt }}</p>
                  </div>
                  <p class=“intro-secondary-slider-slide__button”>
                    <NuxtLink class=“button” :to=“slide.link”>{{ slide.buttonText }}</NuxtLink>
                  </p>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class=“slider-secondary-nav intro-secondary-slider__nav”>
          <button class=“slider-nav__button slider-nav__button--prev” title=“Previous”>
            <SvgIcon name=“arrow-left” />
          </button>
          <span class=“slider-nav__count”><span>1</span>/{{ heroSlides.length }}</span>
          <button class=“slider-nav__button slider-nav__button--next” title=“Next”>
            <SvgIcon name=“arrow-right” />
          </button>
        </div>
      </div>
    </section>

    <!-- Featured Text -->
    <section class=“section border-bottom featured-text-wrapper”>
      <div class=“container”>
        <div class=“featured-text”>
          <div class=“row”>
            <div class=“col-3 md:col-12”>
              <div class=“featured-text__header”>
                <div class=“featured-text__icon”>
                  <SvgIcon name=“rim-icon-primary” />
                </div>
              </div>
            </div>
            <div class=“col-8 md:col-12”>
              <div class=“featured-text__content”>
                <div class=“featured-text__text apply-formatting”>
                  <p>
                    Vyvíjíme a uvádíme na trh židle a nábytek do kancelářského prostředí, 
                    které uspokojí zákazníky svým technickým řešením, designem, cenou a kvalitou.
                  </p>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- Nav Cards -->
    <section class=“section border-bottom nav-cards-wrapper”>
      <SectionHeader title=“Produkty” />
      
      <div class=“container”>
        <div class=“nav-cards nav-cards--categories”>
          <div class=“row”>
            <div class=“col-3 md:col-6”>
              <div class=“section-caption”>
                <nav class=“section-caption__nav”>
                  <ul>
                    <li v-for=“category in categories” :key=“category.slug”>
                      <NuxtLink :to=“`/category/${category.slug}`“>{{ category.name }}</NuxtLink>
                    </li>
                  </ul>
                </nav>
                <NuxtLink to=“/products” class=“button section-caption__button”>
                  Všechny produkty
                </NuxtLink>
              </div>
            </div>

            <NavCard 
              v-for=“card in featuredCategories” 
              :key=“card.id”
              :img=“card.img”
              :title=“card.title”
              :link=“card.link”
              class=“col-3 md:col-6"
            />
          </div>
        </div>
      </div>
    </section>

    <!-- Posts (News Mosaic) -->
    <section class=“section border-bottom posts-wrapper”>
      <SectionHeader title=“Svět Rim” />
      
      <div class=“container”>
        <div class=“posts posts--news posts--mosaic”>
          <div class=“row”>
            <div class=“col-8 md:col-12">
              <PostCard 
                :img=“featuredNews.img”
                :title=“featuredNews.title”
                :excerpt=“featuredNews.excerpt”
                :link=“featuredNews.link”
              />
            </div>
            <div class=“col-4 md:col-12”>
              <PostCard 
                :img=“secondaryNews.img”
                :title=“secondaryNews.title”
                :excerpt=“secondaryNews.excerpt”
                :link=“secondaryNews.link”
              />
            </div>
            <div class=“col-12">
              <PostCard 
                :img=“tertiaryNews.img”
                :title=“tertiaryNews.title”
                :excerpt=“tertiaryNews.excerpt”
                :link=“tertiaryNews.link”
              />
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- Reference Slider -->
    <section class=“section bg-light post-slider-wrapper”>
      <SectionHeader title=“Reference” />
      
      <div class=“container”>
        <div class=“post-slider”>
          <div class=“post-slider__slides owl-carousel”>
            <PostCard 
              v-for=“reference in references” 
              :key=“reference.id”
              :img=“reference.img”
              :title=“reference.title”
              :excerpt=“reference.excerpt”
              :link=“reference.link”
            />
          </div>
          <div class=“slider-nav post-slider__nav”>
            <button class=“slider-nav__button slider-nav__button--prev” title=“Previous”>
              <SvgIcon name=“arrow-left” />
            </button>
            <button class=“slider-nav__button slider-nav__button--next” title=“Next”>
              <SvgIcon name=“arrow-right” />
            </button>
          </div>
        </div>
      </div>
    </section>
  </div>
</template>

<script setup>
// SEO
useSeoMeta({
  title: ‘Rim - Domovská stránka’,
  description: ‘Vyvíjíme a uvádíme na trh židle a nábytek do kancelářského prostředí’
})

// Data (will later come from CMS)
const heroSlides = [
  {
    id: 1,
    type: ‘image’,
    src: ‘root/content/homepage/intro-slider-slide-1.jpg’,
    mobileSrc: ‘root/content/homepage/news-1.jpg’,
    title: ‘Kloe’,
    excerpt: ‘Nové kompletní řešení pro váš prostor’,
    buttonText:Více o kolekci’,
    link:/collection/kloe’,
    modifierClass: ‘intro-secondary-slider-slide--secondary’
  },
  {
    id: 2,
    type: ‘video’,
    src: ‘./root/content/homepage/811-822-pov_fhd_best_2.mp4’,
    poster: ‘./root/content/homepage/intro-slider-slide-1.jpg’,
    title: ‘Lambda’,
    excerpt: ‘Nadčasový design s nádechem přírody prostor prostor’,
    buttonText:Více o kolekci’,
    link:/collection/lambda’
  }
]

const categories = [
  { slug: ‘kancelarske-zidle’, name: ‘Kancelářské židle’ },
  { slug: ‘konferencni-zidle’, name: ‘Konferenční židle’ },
  { slug: ‘kresla’, name:Křesla’ },
  // ... more categories
]

const featuredCategories = [
  {
    id: 1,
    img: ‘root/content/categories/1.jpg’,
    title: ‘Kancelářské židle’,
    link:/category/kancelarske-zidle’
  },
  {
    id: 2,
    img: ‘root/content/categories/2.jpg’,
    title: ‘Konferenční židle’,
    link:/category/konferencni-zidle’
  },
  {
    id: 3,
    img: ‘root/content/categories/3.jpg’,
    title:Křesla’,
    link:/category/kresla’
  }
]

const featuredNews = {
  img: ‘root/content/homepage/news-1.jpg’,
  title: ‘Nový showroom Praha’,
  excerpt:<p>Poprvé v historii soutěže se nám podařilo získat hned dvě ocenění...</p>‘,
  link:/news/novy-showroom-praha’
}

// ... more data objects
</script>

Components

<!-- components/NavCard.vue -->
<template>
  <div class=“nav-card-wrapper”>
    <NuxtLink :to=“link || ‘#’“>
      <div class=“nav-card”>
        <div v-if=“img” class=“cover nav-card__img”>
          <img :src=“img” :alt=“title” />
        </div>
        <div class=“nav-card__content”>
          <h3 v-if=“title” class=“nav-card__title”>{{ title }}</h3>
          <SvgIcon name=“arrow-right” class=“nav-card__icon” />
        </div>
      </div>
    </NuxtLink>
  </div>
</template>

<script setup>
interface Props {
  img?: string
  title?: string
  link?: string
}

defineProps<Props>()
</script>
<!-- components/SectionHeader.vue -->
<template>
  <header class=“section-header”>
    <div class=“container”>
      <div class=“section-header__inner”>
        <div v-if=“subtitle” class=“section-header__subtitle”>{{ subtitle }}</div>
        <h2 v-if=“title” class=“section-header__title”>{{ title }}</h2>
        <div v-if=“perex” class=“section-header__perex apply-formatting” v-html=“perex”></div>
      </div>
    </div>
  </header>
</template>

<script setup>
interface Props {
  title?: string
  subtitle?: string
  perex?: string
}

defineProps<Props>()
</script>
<!-- components/SvgIcon.vue -->
<template>
  <span class=“icon” :class=`icon--${name}`“>
    <svg>
      <use :href=`/public/images/sprite.svg#${name}`“></use>
    </svg>
  </span>
</template>

<script setup>
interface Props {
  name: string
}

defineProps<Props>()
</script>

Header Component

<!-- components/AppHeader.vue -->
<template>
  <header class=“header” id=“header”>
    <div class=“header__top”>
      <div class=“container”>
        <NuxtLink class=“header__logo” to=“/”>
          <SvgIcon name=“logo” />
        </NuxtLink>
        <nav class=“header__nav-secondary”>
          <ul>
            <li><NuxtLink to=“/about”>O nás</NuxtLink></li>
            <li><NuxtLink to=“#” class=“is-active”>Pro architekty</NuxtLink></li>
            <li><NuxtLink to=“/downloads”>Ke stažení</NuxtLink></li>
            <li>
              <a href=“#” class=“dropdown-trigger”>
                SharePoint
                <SvgIcon name=“external-link” />
              </a>
              <div class=“header__dropdown”>
                <ul>
                  <li><a href=“#”>CZ</a></li>
                  <li><a href=“#”>SK</a></li>
                </ul>
              </div>
            </li>
          </ul>
        </nav>
        
        <div class=“lang-switch”>
          <div class=“lang-switch__selected”>
            <SvgIcon name=“globe” />
            {{ currentLang }}
          </div>
          <ul class=“lang-switch__dropdown”>
            <li v-for=“lang in languages” :key=“lang.code”>
              <button 
                @click=“switchLanguage(lang.code)”
                :class=“{ ‘is-active’: currentLang === lang.code }”
              >
                {{ lang.name }}
              </button>
            </li>
          </ul>
        </div>
      </div>
    </div>
    
    <div class=“header__bottom”>
      <div class=“container”>
        <NuxtLink class=“header__logo-secondary” to=“/”>
          <SvgIcon name=“logo” />
        </NuxtLink>
        <nav class=“header__nav-primary”>
          <ul>
            <li>
              <NuxtLink to=“/products”>Produkty</NuxtLink>
              <AppSubmenu />
            </li>
            <li><NuxtLink to=“/designers” :class=“{ ‘is-active’: $route.path.includes(‘designers’) }“>Designéři</NuxtLink></li>
            <li><NuxtLink to=“/inspiration”>Inspirace</NuxtLink></li>
            <li><NuxtLink to=“/references”>Reference</NuxtLink></li>
            <li><NuxtLink to=“/stores”>Prodejny</NuxtLink></li>
            <li><NuxtLink to=“/contact”>Kontakt</NuxtLink></li>
          </ul>
        </nav>
        <div class=“header__actions”>
          <button @click=“openSearch” class=“header-action header-action--search”>
            <SvgIcon name=“search” />
            Vyhledat
          </button>
        </div>
      </div>
    </div>
  </header>
</template>

<script setup>
const route = useRoute()

const currentLang = ref(‘CZ’)
const languages = [
  { code:CZ’, name: ‘Česky’ },
  { code:EN’, name: ‘English’ },
  { code:DE’, name: ‘Deutsch’ }
]

const switchLanguage = (code: string) => {
  currentLang.value = code
  // Handle language switching logic
}

const openSearch = () => {
  // Handle search modal opening
}
</script>

Benefits of This Nuxt Approach

  1. Smooth Vue transition - When you’re ready for CMS integration, you just replace the hardcoded data with API calls
  2. Type safety - Full TypeScript support
  3. Static generation - nuxt generate creates pure HTML files
  4. SEO optimized - Built-in meta management
  5. Modern tooling - Vite, hot reload, dev tools
  6. Component reusability - Perfect for your component-heavy design
  7. Auto-imports - Components and composables auto-imported
  8. File-based routing - Each page is just a file in /pages

Migration Strategy

  1. Start with layout - Convert header/footer first
  2. Move static data - Keep your current content structure, just move data to script sections
  3. Convert components - One by one, starting with simple ones
  4. Keep your SCSS - Works perfectly with Nuxt
  5. Add dynamic features gradually - Search, filtering, etc.

When you’re ready for CMS integration, you just:

  • Replace static data with $fetch() calls to your API
  • Add dynamic routing ([slug].vue files)
  • Keep all your components exactly the same

This approach gives you the best of both worlds - static generation now, easy Vue app transition later. Want me to show you how to set this up or dive deeper into any specific part?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment