+1

Nghiên cứu cơ bản về Nuxt 3

Nuxt là một open source framework dựa trên cốt lõi là VueJS. Nuxt cung cấp một khả năng phát triển website một cách nhanh hơn, thuận tiện hơn. Nuxt hỗ trợ nhiều chức năng giúp tăng performance, UX và quan trọng là giúp tối ưu hóa SEO. Nuxt là một bộ công cụ mạnh mẽ kèm với nhiều thư viện và module được tích hợp giúp cho việc phát triển website trở nên dễ dàng hơn.

Nghiên cứu

Install

pnpm dlx nuxi@latest init <project-name>
npx nuxi@latest init <project-name>

Key concepts

Page

  • Là một file để hiển thị view cho một route xác định.
  • Các page được tạo trong thư mục pages/ Ex:
<template>
  <div>
    <h1>Welcome to the homepage</h1>
    <AppAlert>
      This is an auto-imported component
    </AppAlert>
  </div>
</template>

Layout

  • Là một tập hợp các UI thông dụng cho nhiều page. VD đơn giản là các page sẽ có thể có chung header và footer.
  • <slot /> component để chỉ định đó là nơi sẽ hiển thị nội dung của page
  • Mặc định thì file layouts/default.vue sẽ được sử dụng cho mọi page
  • Để sử dụng custom layout thì thêm file layout vào thư mục layouts/ Ex:
<!-- Custom Layout -->
<template>
  <div>
    <p>Custom Layout</p>
    <slot />
  </div>
</template>
<!-- Custom Layout -->
<script setup lang="ts">default
definePageMeta({
  layout: 'custom'
})
</script>

Component

  • Là những phần UI được chia nhỏ để dễ sử dụng lại tạo ra UI cho các trang
  • Các component được lưu ở trong thư mục components/.
  • Tương tự Layout, component cũng có thể sử dụng <slot/> để hiển thị nội dung được truyền vào
  • Điểm đặc biệt của Nuxt là khi sử dụng component sẽ không cần phải import vào từng file mà Nuxt sẽ tự động hiểu và sử dụng nó dựa trên tên file.

Ex:

<!-- components/AppAlert.vue -->
<template>
  <span>
    <slot />
  </span>
</template>
<template>
  <div>
    <h1>Welcome to the homepage</h1>
    <!-- Theo tên file AppAlert.vue. VD như file components/AppAlert/Header.vue thì sẽ sử dụng component <AppAlertHeader></AppAlertHeader> -->
    <AppAlert> 
      This is an auto-imported component.
    </AppAlert>
  </div>
</template>

Plugin

Asset & Public

  • 2 thư mục chính để lưu các file như image, font, stylesheets là public/assets/
  • Ở thư mục public/ thì người dùng có thể truy cập được file đó qua đường dẫn, còn ở asset thì không
  • Khi sử dụng các file ở assets/ thì Nuxt sẽ sử dụng Vite (mặc định) hoặc webpack để xử lý các file đó và sử dụng trong chương trình
    Ex:
<template>
  <img src="~/assets/img/nuxt.png" alt="Assets folder" />
  <img src="/img/nuxt.png" alt="Public folder" />
</template

Styles

  • Nuxt cung cấp nhiều hỗ trợ cho style UI như: local stylesheets (lưu stylesheets ở local), External stylesheets (cdn)
  • Nuxt cũng cung cấp khả năng sử dụng Preprocessors như SASS
  • Có thể sử dụng Single FIle Components (SFC) styling: có thể viết trực tiếp style vào style block ở trong chính component, đây là một tính năng tuyệt vời của Vue.
  • Một số Library và module hỗ trợ:
    • UnoCSS: Instant on-demand atomic CSS engine
    • Tailwind CSS: Utility-first CSS framework
    • Fontaine: Font metric fallback
    • Pinceau: Adaptable styling framework
    • Nuxt UI: A UI Library for Modern Web Apps
    • Panda CSS: CSS-in-JS engine that generates atomic CSS at build time

Routing

  • Nuxt cung cấp khả năng tự tạo route của file dựa trên đường dẫn của các file trong thư mục page Ex:
| pages/
---| about.vue
---| index.vue
---| posts/
-----| [id].vue
  • Điều hướng: sử dụng component <NuxtLink> để sử dụng chức năng điều hướng
    Ex:
<template>
  <header>
    <nav>
      <ul>
        <li><NuxtLink to="/about">About</NuxtLink></li>
        <li><NuxtLink to="/posts/1">Post 1</NuxtLink></li>
        <li><NuxtLink to="/posts/2">Post 2</NuxtLink></li>
      </ul>
    </nav>
  </header>
</template>
  • Để lấy được thông tin chi tiết của route hiện tại như là các params thì sử dụng composable useRoute()
    Ex:
<script setup lang="ts">
const route = useRoute()

// When accessing /posts/1, route.params.id will be 1
console.log(route.params.id)
</script>
  • Route validation: Nuxt cung cấp khả năng validation cho trang được tích hợp sẵn
    Ex:
<script setup lang="ts">
definePageMeta({
  validate: async (route) => {
    // Check if the id is made up of digits
    return typeof route.params.id === 'string' && /^\d+$/.test(route.params.id)
  }
})
</script>

Middelware

  • Nuxt cung cấp chức năng tạo middleware cho các route, các middleware này sẽ thực thi một chức năng trước khi được chuyển hướng đến một trang
  • Có 3 loại middleware:
    • Anonymous (or inline) route middleware: Middleware được tạo ngay trong chính trang đó và chỉ áp dụng lên cho 1 trang
    • Named route middleware: Middleware được tạo ở trong thư mục middleware/ và sẽ được sử dụng nếu như được config vào 1 trang
    • Global route middleware: Middleware được tạo ở trong thư mục middleware/ với hậu tố .global và sẽ được sử dụng lên mọi trang web
  • Thứ tự áp dụng các middleware (các middleware cùng loại sẽ áp dụng theo thứ tự alphabetical)
    1. Global route middleware
    2. Anonymous (or inline) route middleware
    3. Named route middleware

Ex:

// middleware/my-middleware.ts
export default defineNuxtRouteMiddleware((to, from) => {
  if (to.params.id === '1') {
    return abortNavigation()
  }
  // In a real app you would probably not redirect every route to `/`
  // however it is important to check `to.path` before redirecting or you
  // might get an infinite redirect loop
  if (to.path !== '/') {
    return navigateTo('/')
  }
})

  • Nếu như trang web sử dụng server - rendered hoặc generated thì middleware sẽ được chạy khi trang được render ở server và chạy lại ở phía client. Có thể sử dụng code sau để điều khiển middleware thực thi lúc nào
export default defineNuxtRouteMiddleware(to => {
  // skip middleware on server
  if (process.server) return
  // skip middleware on client side entirely
  if (process.client) return
  // or only skip middleware on initial client load
  const nuxtApp = useNuxtApp()
  if (process.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) return
})

Module

  • Những module code cung cấp sẵn để bổ trợ thêm cho Nuxt, mở rộng cho framework Nuxt với việc bổ sung thêm nhiều chức năng hữu ích. Đồng thời giúp dễ dàng tái sử dụng lại code
  • Cách để thêm module vào cho Nuxt Project
export default defineNuxtConfig({
  modules: [
    // Using package name (recommended usage)
    '@nuxtjs/example',

    // Load a local module
    './modules/example',

    // Add module with inline-options
    ['./modules/example', { token: '123' }],

    // Inline module definition
    async (inlineOptions, nuxt) => { }
  ]
})

State Management

useState() : Default
  • Nuxt cung cấp composable mặc định để quản lý state là useState
  • Có thể hỗ trợ tốt cho SSR Ex:
<script setup lang="ts">
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>

<template>
  <div>
    Counter: {{ counter }}
    <button @click="counter++">
      +
    </button>
    <button @click="counter--">
      -
    </button>
  </div>
</template>
  • Shared State: với việc hỗ trợ auto-imported composables, có thể định nghĩa và sử dụng global state của app như sau
    Ex:
// composables/states.ts
export const useCounter = () => useState<number>('counter', () => 0)
export const useColor = () => useState<string>('color', () => 'pink')
<script setup lang="ts">
// ---cut-start---
const useColor = () => useState<string>('color', () => 'pink')
// ---cut-end---
const color = useColor() // Same as useState('color')
</script>

<template>
  <p>Current color: {{ color }}</p>
</template>

VueX
  • Từng sử dụng ở Nuxt 2 và hiện nay không còn được khuyến nghị sử dụng. Có thể xem thêm chi tiết ở link
Pinia
  • Là module được khuyến nghĩ sử dụng để quản lý state cho Nuxt

Ex:

// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => {
    return { count: 0 }
  },
  // could also be defined as
  // state: () => ({ count: 0 })
  actions: {
    increment() {
      this.count++
    },
  },
})
<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()

counter.count++
// with autocompletion ✨
counter.$patch({ count: counter.count + 1 })
// or using an action instead
counter.increment()
</script>

<template>
  <!-- Access the state directly from the store -->
  <div>Current Count: {{ counter.count }}</div>
</template>

Render Mode

  • Nuxt hỗ trợ các chế độ render page khác nhau như là:  universal renderingclient-side rendering và cũng hỗ trợ hybrid-rendering
  • Mặc định, Nuxt sẽ sử dụng universal rendering để giúp tối ưu hóa trải nghiệm người dụng, tăng performance và quan trọng nhất là hỗ trợ tốt cho SEO
Universal Rendering
  • Khi sử dụng universal rendering, server sẽ trả về một page HTML đã được render xong rồi trả về cho client. Cách render này khá tương tự với server-side rendering của PHP
  • Tuy nhiên bên cạnh đó để giữ được các ưu điểm của client-side rendering như là dynamic interface và page transitions thì sau khi client đã nhận được page HTML đã được render thì sẽ tiếp tục tải ngầm các file javascript và sẽ được chạy và VueJS sẽ kiểm soát các thao tác trên trang
  • Bằng cách sử dụng universal redering sẽ giúp cho trang load nhanh hơn và vẫn giữ được các ưu điểm của client-side redering, đồng thời giúp cho việc SEO của trang web trở nên tốt hơn
  • Minh họa:
Client-Side Rendering
  • Khi sử dụng client-side rendering, một trang HTML rỗng sẽ được load xuống client kèm các đoạn code javascript. Các đoạn code javascript này sẽ đảm nhiệm việc load các nội dung ngay sau đó ở phía client
  • Mặc định thì Nuxt sử dụng universal rendering, để chuyển qua sử dụng client-side rendering bằng cách config:
// nuxt.config.ts
export default defineNuxtConfig({
  ssr: false
})
  • Minh họa:
Hybrid Rendering
  • Theo mặc định thì tất cả mọi route của trang web đều chỉ có thể sử dụng Universal rendering hoặc Client-side rendering. Tuy vậy có một vài trường hợp thì có những trang sẽ cần dùng Universal rendering, một số trang khác cần sử dụng Client-side rendering thì sẽ giúp tối ưu hơn, do đó Nuxt 3 cung cấp Hybrid Rendering giúp có thể tùy chỉnh và kết hợp các chế độ render, cách sử dụng cache cho từng route của website
  • Cách config:
// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // Homepage pre-rendered at build time
    '/': { prerender: true },
    // Products page generated on demand, revalidates in background, cached until API response changes
    '/products': { swr: true },
    // Product page generated on demand, revalidates in background, cached for 1 hour (3600 seconds)
    '/products/**': { swr: 3600 },
    // Blog posts page generated on demand, revalidates in background, cached on CDN for 1 hour (3600 seconds)
    '/blog': { isr: 3600 },
    // Blog post page generated on demand once until next deployment, cached on CDN
    '/blog/**': { isr: true },
    // Admin dashboard renders only on client-side
    '/admin/**': { ssr: false },
    // Add cors headers on API routes
    '/api/**': { cors: true },
    // Redirects legacy urls
    '/old-page': { redirect: '/new-page' }
  }
})
Edge-Side Rendering
  • Edge-Side Rendering sẽ giúp cho trang web có thể tận dụng tối đa khả năng của việc deployment website có sử dụng các edge servers của Content Delivery Network (CDN). Hiểu cách đơn giản thì khi client yêu cầu nội dung trang web, thay vì server gốc trả về nội dung thì sẽ dựa trên vị trí của client mà edge server gần nhất với client đó sẽ trả về nội dung, từ đó giúp giảm độ trễ và tăng tốc độ load trang làm tăng trải nghiệm người dùng
  • Edge-Side Rendering thiên về là một chiến lược deployment website hơn là một chế độ render như 3 chế độ render ở trên

SEO

  • Sử dụng composable useHead để thiết lập các head tags cho trang web
    Ex:
<script setup lang="ts">
useHead({
  title: 'My App',
  meta: [
    { name: 'description', content: 'My amazing site.' }
  ],
  bodyAttrs: {
    class: 'test'
  },
  script: [ { innerHTML: 'console.log(\'Hello world\')' } ]
})
</script>
  • Sử dụng composable useSeoMeta để định nghĩa các thẻ meta phục vụ SEO Ex:
<script setup lang="ts">
useSeoMeta({
  title: 'My Amazing Site',
  ogTitle: 'My Amazing Site',
  description: 'This is my amazing site, let me tell you all about it.',
  ogDescription: 'This is my amazing site, let me tell you all about it.',
  ogImage: 'https://example.com/image.png',
  twitterCard: 'summary_large_image',
})
</script>
  • Một số component được cung cấp: <Title><Base><NoScript><Style><Meta><Link><Body><Html><Head>

  • Dynamic title:
    Ex:

<script setup lang="ts">
useHead({
  // as a string,
  // where `%s` is replaced with the title
  titleTemplate: '%s - Site Title',
})
</script>
  • Gắn script vào cuối thẻ <body>
<script setup lang="ts">
useHead({
  script: [
    {
      src: 'https://third-party-script.com',
      // valid options are: 'head' | 'bodyClose' | 'bodyOpen'
      tagPosition: 'bodyClose'
    }
  ]
})
</script>

Link & Document

Một số link về các resource hữu ích để nghiên cứu, học tập và sử dụng Nuxt

Tài liệu tham khảo


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí