Skip to content

Chapter 3: Designing the Frontend (Nuxt)

Excellent! With a solid database schema and a backend API designed with DDD/CQRS, it's now time to build the user interface with Nuxt.


Core Pages

We will focus on the two most important pages: the post list page and the post detail page.


1. Post List Page (Homepage)

This is the first page a user sees.

  • Rendering Strategy: The content (list of posts) changes when new articles are added but is the same for all users, and SEO is critically important. Therefore, SSR (Server-Side Rendering) is the ideal choice.

  • Implementation in Nuxt:

    • File: pages/index.vue
    • Data Fetching: We will use useAsyncData or useFetch. These functions will run on the server during the initial load to fetch data.
    • Optimization: The API endpoint it calls will be a Query in our CQRS system, returning a "flat," lightweight list of DTOs.

    pages/index.vue

    vue
    <template>
    	<div>
    		<h1>My Awesome Blog</h1>
    		<div v-if="pending">Loading...</div>
    		<div v-else>
    			<div v-for="post in posts" :key="post.id">
    				<NuxtLink :to="`/posts/${post.slug}`">
    					<h2>{{ post.title }}</h2>
    				</NuxtLink>
    				<p>by {{ post.authorUsername }}</p>
    			</div>
    		</div>
    	</div>
    </template>
    
    <script setup>
    // useFetch will automatically call the API on the server (for SSR)
    // and on the client (during page transitions).
    const { data: posts, pending } = await useFetch("/api/posts/latest")
    </script>

2. Post Detail Page

This page displays the content of a single post and its comments.

  • Rendering Strategy:

    • Post Content: Must be rendered on the server (SSR) for SEO and the fastest possible display.
    • Comment Section: Can be loaded on the client (CSR) to avoid slowing down the initial render of the post. This is a Hybrid rendering pattern.
  • Implementation in Nuxt:

    • File: pages/posts/[slug].vue
    • Fetching Post Data: Use useFetch to get the main content of the post on the server.
    • Fetching Comment Data: Create a <CommentSection /> component and fetch the comment data inside it on the client side.

    pages/posts/[slug].vue

    vue
    <template>
    	<div v-if="post">
    		<h1>{{ post.title }}</h1>
    		<p>by {{ post.authorUsername }}</p>
    		<div v-html="post.content"></div>
    		<hr />
    		<h2>Comments</h2>
    		<CommentSection :postId="post.id" />
    	</div>
    </template>
    
    <script setup>
    const route = useRoute()
    const { data: post } = await useFetch(`/api/posts/${route.params.slug}`)
    </script>

3. Analysis of Optimization Decisions

  1. SSR for Main Content: Ensures a fast First Contentful Paint (FCP) and is optimized for SEO.
  2. Hybrid Rendering for Comments: Reduces the load on the server and improves FCP by only loading the main content initially. The comments section (often secondary) is loaded afterward.
  3. Code Splitting: Nuxt automatically splits the code for each page, so users only download the necessary JavaScript.
  4. Prefetching: When a user hovers over a <NuxtLink>, Nuxt will automatically prefetch the data for that page in the background, making page transitions feel almost instantaneous.
  5. Leveraging Backend CQRS: The frontend calls optimized Query endpoints that return only compact DTOs, reducing bandwidth and speeding up JSON parsing.

Conclusion:

We have designed a complete system from end to end. By combining a well-designed database, a powerful CQRS backend API, and a modern Nuxt frontend, we have created an application that not only has a good, maintainable architecture but also boasts extremely high performance at every layer.