Nuxt UI Portfolio Template

repository·main·Indexed 42 days ago

https://github.com/nuxt-ui-templates/portfolio

A professional portfolio template built with Nuxt and Nuxt UI, featuring a blog powered by Nuxt Content. Includes pre-configured schemas for projects, speaking engagements, and testimonials, with customizable theme colors and profile settings via app.config.ts.

Tokens
4.4K
Snippets
16
Records
19
Agent score
54%

What's inside Nuxt UI Portfolio Template

  1. Quick Start with the Nuxt Portfolio Template

    main

    To create a new project using the Nuxt Portfolio template, use the nuxi CLI via npm create nuxt. This will scaffold a new Nuxt application pre-configured with the portfolio template and Nuxt UI.

    npm create nuxt@latest -- -t ui/portfolio
  2. Build and Preview for Production

    main

    To prepare your portfolio for deployment, build the application for production. You can also preview the production build locally to ensure everything works correctly before deploying.

    # Build the application
    pnpm build
    
    # Locally preview the production build
    pnpm preview
  3. Structure of the Home Page (index.vue)

    main

    The home page (app/pages/index.vue) is a data-driven route that fetches content from a collection named index using Nuxt Content/Collections. It uses useAsyncData to retrieve the page data and useSeoMeta to dynamically set SEO metadata based on the fetched content.

    If the page data is not found, it throws a fatal 404 error. The template renders a series of landing components that consume the fetched page data:

    • LandingHero
    • LandingAbout
    • LandingWorkExperience
    • LandingBlog
    • LandingTestimonials
    • LandingFAQ
    <template>
      <UPage v-if="page">
        <LandingHero :page />
        <UPageSection :ui="{ container: 'pt-0! lg:grid lg:grid-cols-2 lg:gap-8' }">
          <LandingAbout :page />
          <LandingWorkExperience :page />
        </UPageSection>
        <LandingBlog :page />
        <LandingTestimonials :page />
        <LandingFAQ :page />
      </UPage>
    </template>
  4. Root application structure and layout in app.vue

    main

    The root component app.vue defines the global application shell. It uses <UApp> as the top-level wrapper, followed by <NuxtLayout> for page layouts and <UMain> to wrap the <NuxtPage /> content. A global search component <LazyUContentSearch> is rendered inside a <ClientOnly> block to ensure it only runs on the client side, preventing hydration mismatches.

    Key global behaviors include:

    • Dynamic Theme Color: The theme-color meta tag is dynamically updated based on the current useColorMode() value.
    • SEO Configuration: A global titleTemplate is set to %s - Nuxt Portfolio Template via useSeoMeta.
    • Data Fetching: Navigation and search data are fetched at the root level using useAsyncData and useLazyAsyncData to populate the global search functionality.
    <template>
      <UApp>
        <NuxtLayout>
          <UMain class="relative">
            <NuxtPage />
          </UMain>
        </NuxtLayout>
    
        <ClientOnly>
          <LazyUContentSearch
            :files="files"
            :navigation="navigation"
            :links="navLinks"
            :fuse="{ resultLimit: 42 }"
          />
        </ClientOnly>
      </UApp>
    </template>
  5. Implement a blog listing page using Nuxt Content Collections

    main

    To create a blog listing page, use useAsyncData to fetch two distinct datasets from the content collections:

    1. The Page Metadata: Fetch the specific page configuration (SEO, title, description) using queryCollection('pages').path('/blog').first().
    2. The Blog Posts: Fetch all posts from the blog collection, ordered by date in descending order using queryCollection('blog').order('date', 'DESC').all().

    If either the page or the posts are not found, you should throw a fatal 404 error using createError to ensure the Nuxt application handles the missing content correctly.

    // Fetch page metadata
    const { data: page } = await useAsyncData('blog-page', () => {
      return queryCollection('pages').path('/blog').first()
    })
    
    // Fetch blog posts
    const { data: posts } = await useAsyncData('blogs', () =>
      queryCollection('blog').order('date', 'DESC').all()
    )
    
    // Error handling if content is missing
    if (!page.value || !posts.value) {
      throw createError({
        statusCode: 404,
        statusMessage: 'Page not found',
        fatal: true
      })
    }
  6. Configure SEO for the blog listing page

    main

    The blog listing page dynamically sets SEO metadata based on the fetched page content. It prioritizes seo.title and seo.description from the content collection, falling back to the standard title and description fields if the SEO-specific fields are absent.

    Use useSeoMeta to apply these values to the document and Open Graph tags, and defineOgImage to set the social sharing image.

    const title = page.value?.seo?.title || page.value?.title
    const description = page.value?.seo?.description || page.value?.description
    
    useSeoMeta({
      title,
      ogTitle: title,
      description,
      ogDescription: description
    })
    
    defineOgImage('Portfolio', { title, description })
  7. Configure ESLint for the Nuxt Portfolio Template

    main

    The project uses a flat configuration format via eslint.config.mjs. It leverages withNuxt to integrate Nuxt-specific linting rules and eslint-plugin-better-tailwindcss to enforce Tailwind CSS best practices.

    To customize linting, you can modify the rules object or the settings for the Tailwind plugin. Note that the configuration is wrapped in withNuxt(), which provides the base Nuxt environment settings.

    import withNuxt from './.nuxt/eslint.config.mjs'
    import betterTailwindcss from 'eslint-plugin-better-tailwindcss'
    import { getDefaultAttributes } from 'eslint-plugin-better-tailwindcss/api/defaults'
    
    export default withNuxt(
      {
        rules: {
          '@typescript-eslint/no-explicit-any': 'off'
        }
      },
      betterTailwindcss.configs['correctness-error'],
      {
        settings: {
          'better-tailwindcss': {
            entryPoint: 'app/assets/css/main.css',
            attributes: [
              ...getDefaultAttributes(),
              ['^v-bind:ui$', [{ match: 'objectValues' }]]
            ]
          }
        }
      }
    )
  8. Configure UI colors and Page Hero styling

    main

    The ui key in app.config.ts allows you to customize the theme colors and the layout of the Page Hero component via Tailwind CSS classes.

    Colors

    • primary: The primary brand color (e.g., 'blue').
    • neutral: The neutral color scale used for backgrounds and text.

    Page Hero Slots

    You can override the Tailwind classes for specific slots in the pageHero component:

    • container: Padding and layout for the hero section.
    • title: Styling for the hero title.
    • description: Styling for the hero description text.
    export default defineAppConfig({
      ui: {
        colors: {
          primary: 'blue',
          neutral: 'neutral'
        },
        pageHero: {
          slots: {
            container: 'py-18 sm:py-24 lg:py-32',
            title: 'mx-auto max-w-xl text-pretty text-3xl sm:text-4xl lg:text-5xl',
            description: 'mt-2 text-md mx-auto max-w-2xl text-pretty sm:text-md text-muted'
          }
        }
      }
    })