Phương pháp custom route cho bài viết trong Nuxtjs

Bài viết này sẽ hướng dẫn cách cấu hình đường dẫn linh động với Nuxtjs, hay còn gọi là custom route để tạo mã định danh cho bài viết và đưa vào đường dẫn, áp dụng cho cả hai cơ chế Server-side Rendering và Static Site Generator.

This post is also available in English

Upload image

Các đường dẫn giả mạo xuất hiện ngày càng nhiều, vì vậy người dùng thường có thói quen kiểm tra trước khi quyết định có nên truy cập trang web hay không. Vì thế, các trang tin hay blog sẽ nhúng tựa đề bài viết vào đường dẫn, giúp người dùng nắm được khái quát nội dung, tăng tính thống nhất và độ tin tưởng của bài viết. Tuy nhiên, khuyết điểm của cách làm này đó là khi cần cập nhật tiêu đề của bài viết thì đường dẫn tới bài viết đó cũng sẽ thay đổi, đường dẫn cũ sẽ không thể truy cập được, gây khó chịu cho người dùng. Ngoài ra, chúng ta còn phải cập nhật tất cả các bài viết có liên kết tới bài viết đã đổi tên.

Việc cấu hình đường dẫn linh động cho một bài viết hay trang tin đã trở thành điều kiện hầu như phải có bởi độ linh hoạt và thân thiện với người dùng. Để giải quyết bài toán này, ta có thể áp dụng phương pháp tạo ra một mã định danh cho bài viết và đưa nó vào trong đường dẫn. Mình sẽ gọi phương pháp này là custom route.

Với việc dùng mã định danh, khi tựa đề bị thay đổi thì trang web vẫn có thể hiển thị đúng bài viết cần tìm dù người dùng truy cập vào bài viết từ đường dẫn cũ. Chúng ta sẽ cùng tìm hiểu cách để áp dụng custom route trong cả hai cơ chế Server-side Rendering và Static Site Generator với Nuxtjs.

Khởi tạo project

Nếu các bạn đã có project và chỉ muốn tìm hiểu cách để áp dụng custom route thì có thể bỏ qua phần này.

Bài viết chỉ tập trung vào việc thiết lập đường dẫn linh động cho bài viết nên sẽ không hướng dẫn cụ thể cách tạo một project Nuxtjs. Nếu mới bắt đầu, các bạn có thể bắt đầu tại đây.

Sau khi đã khởi tạo project, ta tiến hành tạo ra một số bài viết lưu trong thư mục content/blog. File được lưu dưới định dạng markdown, với tên file là mã định danh duy nhất của bài viết. Trong thực tế, các bài viết sẽ được tạo/chỉnh sửa bằng các CMS tool như Netlify, Hugo hay Wordpress.

Upload image

Dưới đây là định dạng đơn giản của file markdown:

---
title: Apply custom route for blog in Nuxtjs
subtitle: Subtitle
---
Content

Ta sẽ dùng thư viện Nuxt Content để đọc file bài viết dưới định dạng markdown, bắt đầu bằng việc import thư viện vào trong project:

yarn add @nuxt/content

Sau đó, thêm thư viện @nuxt/content vào trong cấu hình modules trong file nuxt.config.js:

{
  …
  modules: \[
    '@nuxt/content'
  ],}

Tạo trang cho bài viết

Tạo file bài viết ở pages/_slug/index.vue, trong đó, slug là đường dẫn liên kết với bài viết:

Upload image

Tại trang bài viết, ta tiến hành lấy mã định danh bài viết và tải về nội dung trong hàm asyncData():

async asyncData({ $content, params }) {
  // Lấy post ID từ slug
  const postID = params.slug.split('-').pop()
  // Tải về nội dung của bài viết
  const post = await $content('blog', postID).fetch().catch(_ => {})
  return {
    post
  }
}

Tiếp theo, ta tiến hành kiểm tra xem mã định danh có hợp lệ hay không và cập nhật lại đường dẫn nếu cần:

async mounted() {
  // Kiểm tra xem bài viết có tồn tại hay không
  if (!this.post) {
    window.location.replace('/404') // Đưa về trang 404
  } else {
    // Khởi tạo đường dẫn bài viết theo tiêu đề
    const postID = this.$route.params.slug.split('-').pop()
    const urlTitle = this.post.title
      .toLowerCase()
      .replace(/ /g, '-')
      .replace(/[^\w-]+/g, '')
    const slug = `${urlTitle}-${postID}`
    // Kiểm tra xem đường dẫn có khớp với đường dẫn hiện tại hay không
    // Nếu không, ta tiến hành cập nhật lại đường dẫn
    if (slug !== this.$route.params.slug) {
      // Sử dụng pushState để tránh page reload
      window.history.pushState('', '', slug)
    }
  }
},

Thêm vài bước để render trang web:

<template>
  <div>
    <h1>{{post ? post.title : ''}}</h1>
    <h2>{{post ? post.subtitle : ''}}</h2>
    <nuxt-content :document="post" />
  </div>
</template>

Vậy là ta đã tạo ra một trang cho bài viết với đường dẫn linh động theo tiêu đề. Các bạn có thể tiến hành chạy thử bằng lệnh yarn dev hoặc tổ hợp yarn buildyarn start. Nếu ta bỏ đi vài ký tự tiêu đề trong đường dẫn thì trang web sẽ tự sửa lại đường dẫn đúng, còn nếu ta thay đổi mã định danh thì sẽ được chuyển sang trang 404.

Upload image

Chúng ta có thể thấy tính năng này đã chạy ổn trong cơ chế Server-side Rendering.

Áp dụng trong cơ chế Static Site Generator

Nếu đang sử dụng cơ chế Static Site Generator để build trang web, điều đầu tiên cần làm là tạo ra đường dẫn cho các bài viết để Nuxtjs khởi tạo trang bài viết trong quá trình xây dựng bản web tĩnh.

Trong file nuxtjs.config.js:

…
target: 'static', // Kích hoạt full static mode
generate: {
  async routes() {
    const { $content } = require('@nuxt/content')
    // Lấy ra tất cả bài viết
    const files = await $content('blog').only(['slug', 'title']).fetch()
    // Trả về danh sách đường dẫn để Nuxtjs khởi tạo trang bài viết
    return files.map(
      file => `/${file.title.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '')}-${file.slug}`
    )
  },
  crawler: false
},

Sau khi chạy lệnh yarn generate, website sẽ được build và lưu trữ trong thư mục dist.

Trong quá trình host web sẽ xuất hiện tình trạng trang web được đưa về trang 404 khi người dùng nhập sai vài ký tự trong thanh địa chỉ, mặc dù mã định danh của bài viết vẫn đúng. Đây không phải là lỗi của Nuxtjs mà là do các tool host static web chỉ map các đường dẫn tới các đường dẫn thư mục trong dist, và đưa về trang 404 nếu như không tìm thấy.

Việc khắc phục tình trạng này tùy thuộc vào tool host web mà chúng ta đang sử dụng. Sau đây là một trong những cách xử lý:

  1. Khởi tạo một trang đệm với content trống.
  2. Cấu hình tool host web để trỏ vào trang này khi không tìm thấy bài viết.
  3. Ở trang đệm này, kiểm tra xem bài viết có hợp lệ hay không. Nếu có, đưa về trang bài viết, nếu không thì đưa về trang 404.

Sau đây là một ví dụ khi host trang web bằng http-server, giả sử ta tạo ra một trang đệm là pages/blank.vue.

Nội dung trang pages/blank.vue:

<template>
  <div></div>
</template>
<script>
export default {
  name: 'BlankPost',
  async mounted() {
    // Lấy post ID từ url , Ex: "http://localhost:8080/blank/?%2F-fake-202206280909" => "202206280909"
    const postID = this.$route.fullPath.split('?').pop().replaceAll('%2F', '').split('-').pop()
    // Tải về nội dung của bài viết
    const post = await this.$content('blog', postID).fetch().catch(_ => {})
    // Kiểm tra xem bài viết có tồn tại hay không
    if (post) {
      // Đưa về trang bài viết
      const urlTitle = post.title
        .toLowerCase()
        .replace(/ /g, '-')
        .replace(/[^\w-]+/g, '')
      const slug = `${urlTitle}-${postID}`
      window.location.replace(`/${slug}`)
    } else {
      // Đưa về trang 404
      window.location.replace('/404')
    }
  }
}
</script>

Chạy trang web và xem kết quả qua các bước: yarn generate, cd disthttp-server -P http://localhost:8080/blank?.

Kết

Trên đây là hướng dẫn cấu hình đường dẫn linh động cho một bài viết hay trang tin bằng Nuxtjs. Với phương pháp custom route này, người dùng vẫn có thể truy cập bài viết bằng đường dẫn cũ dù chúng ta đã thay đổi một tựa đề mới. Cơ chế này càng có ý nghĩa hơn đối với những bài viết mang tính tường thuật trực tiếp (live) khi cần phải thay đổi tựa đề nhiều lần trong một bài viết.

Phần quản lý nội dung của bài post không được đề cập trong bài viết này, nếu bạn muốn xây dựng một hệ thống hoàn chỉnh, hãy bắt đầu bằng cách tìm đến các CMS tool điển hình như Netlify CMS, Flextype hay Hugo.

Hy vọng bài viết này sẽ giúp ích cho các dự án tương tự sắp tới của bạn.

Atekco - Home for Authentic Technical Consultants