NEURAL.VN
Neural Việt Nam

Chia sẻ công nghệ: Website NeuralVN được tạo ra thế nào?

12/3/202321phút đọc

Web Development

Website của NeuralVN được ra đời với mục đích trở thành nơi chia sẻ các kiến thức về lập trình, khoa học dữ liệu và trí tuệ nhân tạo uy tín tại Việt Nam. Sau hơn 1 tháng kể từ lúc lên kế hoạch và bắt đầu thực hiện dự án, hôm nay, chúng tôi muốn chia sẻ đến các bạn những công nghệ đã tạo nên website của chúng tôi, và cách chúng được kết hợp với nhau.

Untitled

Đầu tiên, chúng ta hãy điểm lại những tính năng chính đã có mặt trong phiên bản này:

  • Bài viết: Các chia sẻ kiến thức từ team NeuralVN và khách mời, giúp chia sẻ và lan toả tri thức đến mọi người. Chuyên mục bài viết còn giúp website của chúng tôi dễ dàng được tìm thấy trên các công cụ tìm kiếm (ví dụ Google).
  • Video: Để giúp mọi người xem được video trên chính NeuralVN, chúng tôi xây dựng một trang xem video với các tính năng cơ bản.
  • Khoá học: Trong tương lai, NeuralVN mong muốn xây dựng và chia sẻ kiến thức thành từng series hay khoá học, giúp cộng đồng có thể tiếp cận một cách có hệ thống. Các công nghệ của website cũng được xây dựng để tối ưu việc vận dụng và thực hành kiến thức. Hệ thống quiz và bài tập thực hành giúp các bạn có thể kiểm tra lại kiến thức và thực hành ngay các câu lệnh lập trình trên website của NeuralVN.

Website của NeuralVN được xây dựng với 3 tiêu chí:

  • Tối ưu nội dung: Website được thiết kế và bố trí để truyền tải các nội dung một cách tốt nhất đến người sử dụng, nhấn mạnh vào việc trình bày nội dung, và không cầu kì, diêm dúa quá mức cần thiết.
  • Tối ưu tốc độ: từng trang trong website NeuralVN được thiết kế và tối ưu tỉ mỉ để đảm bảo tuân thủ tốt các khuyến nghị từ các tiêu chuẩn web. Chúng tôi sử dụng công cụ Google PageSpeed Insights để phân tích và đánh giá về mặt tốc độ cho website của mình.
  • Tối ưu về chi phí: việc lựa chọn công nghệ và cơ sở hạ tầng của website phải đảm bảo tối ưu chi phí hoạt động, có thể phục vụ được nhiều người dùng với chi phí duy trì thấp. Điều này giúp chúng tôi có cơ hội phát triển và phục vụ nhiều nội dung miễn phí hơn cho cộng đồng.

Untitled

Tốc độ website NeuralVN - Blog được đánh giá bởi Google PageSpeed Insights

Hãy cùng tìm hiểu về các công nghệ và cách NeuralVN sử dụng chúng trong từng mục bên dưới nhé!

1. Tổng quan về công nghệ

Frontend: Next.js + TailwindCSS

Về cơ bản, website NeuralVN được xây dựng với Next.js, một framework của ReactJS với rất nhiều đặc điểm vượt trội:

  • Dựa trên React: Chắc hẳn các bạn làm frontend đều biết đến React, một thư viện mạnh mẽ để xây dựng frontend với thành phần (component) có thể được sử dụng lại. Việc website được xây dựng trên ReactJS sẽ cung cấp một kho đồ sộ các mã nguồn mở, các thành phần được xây dựng sẵn bởi cộng đồng. Vì thế, việc phát triển web trở nên nhẹ nhàng và tiết kiệm thời gian hơn rất nhiều.
  • Tự động tối thiểu hoá (minify) và gói (bundle) các thành phần trang web, giúp giảm kích thước và số lượng yêu cầu (request) đến máy chủ.

Next.js kết hợp các thành phần của trang web - hình ảnh từ website nextjs.org.

Next.js kết hợp các thành phần của trang web - hình ảnh từ website nextjs.org.

  • Tự động chia tách (auto splitting) trang web thành các thành phần khác nhau, hạn chế việc trình duyệt phải tải lại các thành phần giống nhau nhiều lần (ví dụ khi header và footer của 2 trang giống hệt nhau, các thành phần này sẽ không cần phải tải lại). Việc chia tách này có thể được tối ưu hơn nữa khi kết hợp với việc tự động tải trước (prefetching) các trang có thể người dùng sẽ tới, hoặc trì hoãn việc tải các thành phần chưa dùng đến (xem dynamic import).

    Next.js tự động tải các thành phần - hình ảnh từ website nextjs.org.

    Next.js tự động tải các thành phần - hình ảnh từ website nextjs.org.

  • Nhiều cách lấy dữ liệu (data fetching) và kết xuất (render) trang khác nhau, có thể được tận dụng để tăng tốc độ tải trang và bảo vệ dữ liệu:

    • Kết xuất trang từ phía máy chủ - Server-Side Rendering (SSR): Phương pháp này này có thể giúp ta xử lý nhiều logic phía máy chủ như truy cập CSDL, hoặc xác thực người dùng. Đồng thời, phương pháp này giúp giảm thời gian render phía trình duyệt và tối ưu tham số Cumulative Layout Shift (CLS) cho website (dịch chuyển bố cục khi tải trang).
    • Kết xuất trang tĩnh (getStaticProps, getStaticPaths): Giả sử bạn có một số trang / thành phần không thay đổi và có thể được kết xuất từ khi triển khai trang web, bạn có thể sử dụng phương pháp này để tăng tốc độ tải trang lên đáng kể và giảm gánh nặng cho máy chủ. NeuralVN sử dụng cách kết xuất này cho các bài viết, giúp lấy dữ liệu bài viết từ markdown và tạo thành các trang web tĩnh từ lúc sinh (build) trang.
    • Kết xuất tăng dần (Incremental Static Regeneration): Giả sử bạn có một nguồn thông tin (API, CMS) có thể được dùng để sinh ra các bài viết trên website, tính năng này sẽ được thiết lập để lấy dữ liệu từ các nguồn này, sinh thành các bài viết khi có yêu cầu từ trình duyệt và gửi lại cho trình duyệt. Việc lấy dữ liệu và tạo trang web kiểu này có thể được thiết lập caching, lưu lại các kết quả tạo trang và không tạo lại sau một thời gian nhất định. Cách render này xử lý được hạn chế không thể cập nhật nội dung khi kết xuất trang tĩnh, hoặc mất thời gian trên mỗi yêu cầu (request) khi sử dụng server-side rendering.
    • Kết xuất trang ở trình duyệt (Client side): Phương pháp này được sử dụng khi bạn không cần tối ưu quá nhiều về tốc độ trang bởi chúng ta sẽ tốn thêm thời gian tải dữ liệu và chạy kết xuất với trình duyệt.
  • Next.js có thể tự động tối ưu nhiều đối tượng khác trên trang web như hình ảnh (Image Optimization) và phông chữ (Font Optimization).

  • Next.js không chỉ là frontend. Nó còn được sử dụng để viết các serverless API (xem Edge API). Tính năng này giúp giảm thiểu việc dựng một backend cho các tính năng của hệ thống. Mọi thứ được gói gọn trong Next.js.

  • Hosting miễn phí: NeuralVN đang sử dụng dịch vụ của Vercel, công ty tạo ra Next.js để thực hiện việc hosting. Tài khoản miễn phí được hỗ trợ đến 6000 phút build mỗi tháng và 100GB băng thông (có thể nâng lên 1TB mỗi tháng với 20 USD / tháng). Việc triển khai sẽ được thực hiện tự động khi đẩy mã nguồn lên Github. Nền tảng này giúp chúng tôi tiết kiệm công sức để xây dựng và bảo trì máy chủ.

Với một loạt các lợi ích như trên, Next.js rất phù hợp với dự án NeuralVN và được chúng tôi lựa chọn để xây dựng dự án.

TailwindCSS

TailwindCSS là một framework cho frontend khá hot hiện nay. Có thể hình dung TailwindCSS như việc đưa Bootstrap vào trong React. Việc chúng ta lên style CSS cho một trang web tương tự cách dùng bootstrap: sử dụng các class. Chúng tôi thấy việc sử dụng TailwindCSS giúp đơn giản hoá việc xây dựng style cho trang web rất nhiều, và quyết định sử dụng framework này cho NeuralVN.

<figure className="md:flex bg-slate-100 p-8 md:p-0">
  <img className="w-24 h-24 md:w-48 md:h-auto md:rounded-none mx-auto" src="/sarah-dayan.jpg" alt="" width="384" height="512">
  <div className="pt-6 md:p-8 text-center md:text-left space-y-4">
    <blockquote>
      <p className="text-lg font-medium">
        “Tailwind CSS is the only framework that I've seen scale
        on large teams. It’s easy to customize, adapts to any design,
        and the build size is tiny.”
      </p>
    </blockquote>
    <figcaption className="font-medium">
      <div className="text-sky-500">
        Sarah Dayan
      </div>
      <div className="text-slate-700">
        Staff Engineer, Algolia
      </div>
    </figcaption>
  </div>
</figure>

Backend: Serverless APIs + Firebase

Để đơn giản hoá hệ thống mà vẫn giữ được khả năng mở rộng, chúng tôi sử dụng backend serverless trên nền tảng hosting của Vercel. Trên nền tảng này, các function API sẽ nhận và xử lý các request từ người dùng mà không yêu cầu thiết lập một hệ thống máy chủ chạy 24/7. Cơ sở hạ tầng của Vercel sẽ tự động quản lý việc mở rộng dựa trên số lượng người dùng. Các bạn có thể đọc thêm về serverless functions trên Vercel tại đây.

Serverless functions trong cơ sở hạ tầng của Vercel. Hình ảnh từ https://vercel.com/docs/concepts/functions/serverless-functions.

Serverless functions trong cơ sở hạ tầng của Vercel. Hình ảnh từ https://vercel.com/docs/concepts/functions/serverless-functions.

Ngoài dịch vụ backend, chúng tôi sử dụng thêm Google Firebase - Firestore làm cơ sở dữ liệu cho hệ thống (lưu trữ các thông tin người dùng). Hệ thống xác thực của NeuralVN là sự kết hợp của NextAuth (triển khai dưới dạng serverless functions) và Firebase để lưu trữ thông tin người dùng. Việc mở rộng (scaling) cơ sở dữ liệu trong hệ thống này cũng được thực hiện tự động bởi Google Firebase.

Thiết kế hệ thống của NeuralVN.

Thiết kế hệ thống của NeuralVN.

Có thể nói, với thiết kế serverless + cơ sở dữ liệu Firebase, chúng tôi có thể tự tin với khả năng mở rộng hệ thống của mình. Hơn nữa, khi số lượng người dùng chưa nhiều, hệ thống sẽ không mất quá nhiều chi phí để phục vụ người dùng (dựa trên bảng giá của Vercel và Google Firebase).

2. Trang bài viết

Untitled

Trang bài viết của NeuralVN là nơi chúng tôi chia sẻ các kiến thức về lập trình và trí tuệ nhân tạo đến mọi người. Vì các bài viết này ban đầu sẽ được cập nhật bởi team NeuralVN nên chúng tôi chưa ưu tiên việc thiết kế giao diện viết bài, mà sẽ sinh các bài viết trực tiếp từ các tệp markdown. Hướng tiếp cận này còn giúp chúng tôi sử dụng các trang tĩnh (static page) cho các bài viết, tối ưu điểm hiệu suất trên các trang này.

Kết xuất bài viết từ markdown

Plugin được sử dụng để chuyển từ markdown sang React là mdx-bundler. Cách sử dụng hết sức đơn giản:

import {bundleMDX} from 'mdx-bundler'

const mdxSource = `
---
title: Example Post
published: 2021-02-13
description: This is some description
---

# Wahoo

import Demo from './demo'

Here's a **neat** demo:

<Demo />
`.trim()

const result = await bundleMDX({
  source: mdxSource,
  files: {
    './demo.tsx': `
import * as React from 'react'

function Demo() {
  return <div>Neat demo!</div>
}

export default Demo
    `,
  },
})

const {code, frontmatter} = result

mdxSource là một nội dung markdown kèm theo frontmatter (dữ liệu metadata của bài viết như tiêu đề, mô tả, loại bài viết, ...). Nội dung này sẽ được chuyển thành mã HTML thông qua lệnh await bundleMDX(). Dưới đây là một bài viết của NeuralVN.

Mã nguồn một bài viết tại NeuralVN

Mã nguồn một bài viết tại NeuralVN

Trong quá trình chuyển từ markdown sang HTML, chúng tôi có sử dụng thêm các gói remark và rehype để thực hiện một thao tác như trích xuất mục lục, lên màu cho mã nguồn hay xử lý công thức toán. Sau đây là mã nguồn đang được sử dụng.

import { bundleMDX } from 'mdx-bundler';
// Remark packages
import remarkGfm from 'remark-gfm';
import remarkFootnotes from 'remark-footnotes';
import remarkMath from 'remark-math';
import remarkExtractFrontmatter from './remark-extract-frontmatter';
import remarkCodeTitles from './remark-code-title';
import remarkTocHeadings from './remark-toc-headings';
import remarkImgToJsx from './remark-img-to-jsx';
// Rehype packages
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypeKatex from 'rehype-katex';
import rehypeCitation from 'rehype-citation';
import rehypePrismPlus from 'rehype-prism-plus';
import rehypePresetMinify from 'rehype-preset-minify';

...
let toc = [];
const { code, frontmatter } = await bundleMDX({
  source,
  cwd: path.join(root, 'components'),
  xdmOptions(options, frontmatter) {
    options.remarkPlugins = [
      ...(options.remarkPlugins ?? []),
      remarkExtractFrontmatter,
      [remarkTocHeadings, { exportRef: toc }],
      remarkGfm,
      remarkCodeTitles,
      [remarkFootnotes, { inlineNotes: true }],
      remarkMath,
      remarkImgToJsx,
    ];
    options.rehypePlugins = [
      ...(options.rehypePlugins ?? []),
      rehypeSlug,
      rehypeAutolinkHeadings.bind(null, {
        behavior: 'wrap',
        properties: { className: 'anchor' },
      }),
      rehypeKatex,
      [rehypeCitation, { path: path.join(root, 'data') }],
      [rehypePrismPlus, { ignoreMissing: true }],
      rehypePresetMinify,
    ];
    return options;
  },
...
});
...

Trong NeuralVN, ngoài các thành phần HTML cơ bản, chúng tôi còn sử dụng các React component trong tệp markdown, ví dụ trình phát video, CodeBlock hay Quiz (cho khoá học). Các thành phần này được thêm vào thông qua MDXLayoutRenderer như dưới.

import { useMemo } from 'react';
import { getMDXComponent } from 'mdx-bundler/client';
import Image from './Image';
import CustomLink from './Link';
import TOCInline from './TOCInline';
import CodeBlock from '../code/CodeBlock';
import { BlogNewsletterForm } from '../NewsletterForm';
import CerificateIssue from 'components/certificates/CerificateIssue';
import Quiz from 'components/quiz/Quiz';
import dynamic from 'next/dynamic';
const ReactPlayer = dynamic(() => import('react-player/lazy'), { ssr: false });

export const MDXComponents = {
  Image,
  TOCInline,
  a: CustomLink,
  CodeBlock: CodeBlock,
  Quiz: Quiz,
  BlogNewsletterForm: BlogNewsletterForm,
  ReactPlayer: ReactPlayer,
  CerificateIssue: CerificateIssue,
};

export const MDXLayoutRenderer = ({ layout, mdxSource, ...rest }) => {
  const MDXLayout = useMemo(() => getMDXComponent(mdxSource), [mdxSource]);
  return <MDXLayout components={MDXComponents} {...rest} />;
};

Hệ thống bình luận cho bài viết

Tính năng bình luận của trang sử dụng dịch vụ của Disqus. Việc sử dụng dịch vụ này sẽ giúp chúng tôi bỏ qua phần cài đặt hệ thống bình luận phức tạp cho bài viết, tập trung vào xây dựng nội dung và các thành phần quan trọng hơn. Để mở tính năng bình luận, bạn cần click vào nút bình luận như hình dưới. Disqus sẽ được import động với tính năng dynamic import của Next.js. Việc này giúp tránh tải các thành phần của Disqus khi người dùng chưa có nhu cầu mở bình luận.

Người dùng ấn vào nút Bình luận để mở tính năng bình luận

Người dùng ấn vào nút Bình luận để mở tính năng bình luận

Tính năng bình luận được mở ở bên phải trang web. Ở đây, người dùng có thể bình luận hoặc cùng nhau thảo luận về nội dung bài viết.

Tính năng bình luận được mở ở bên phải trang web. Ở đây, người dùng có thể bình luận hoặc cùng nhau thảo luận về nội dung bài viết.

3. Trang video

Untitled

Trang xem video của NeuralVN sử dụng nguồn video là youtube. Trong quá trình xem, các bạn có thể lựa chọn các video khác nhau trong phần Xem thêm. Tính năng này chủ yếu có nhiệm vụ giúp người xem có thể xem được các video liên quan đến nhau, đúng chủ đề mà NeuralVN hướng tới. Để tối ưu việc nhúng video video trong trang này, chúng tôi sử dụng gói react-player. Plugin này sẽ giúp chúng ta nhúng video từ nhiều nguồn khác nhau như Facebook, Youtube, Soundcloud, Vimeo, hay tệp. Ngoài ra, tuỳ chọn lazy loading hoặc chế độ light cũng giúp tối ưu điểm Google PageSpeed Insights. Dưới đây là một đoạn mã nhúng video sử dụng react-player.

import ReactPlayer from 'react-player';
<ReactPlayer
  url='https://www.youtube.com/embed/A13uzLLlKww'
  light={true}
  controls={true}
  playbackRate={1}
  playing={true}
  autoPlay={true}
/>

Trong tương lai, chúng tôi sẽ bổ sung nhiều chức năng như đánh giá, bình luận cho video để mọi người có thể đưa ra ý kiến cá nhân của mình. Ngoài ra, chúng tôi sẽ triển khai thêm chức năng khuyến nghị video tối ưu cho từng cá nhân.

4. Trang học tập

Trang học tập được xác định là một trong những tính năng cốt lõi của trang web, nơi chúng tôi có thể tổ chức các kiến thức dưới dạng các khoá học trong thời gian sắp tới. Các bài học được xây dựng bao gồm nhiều thành phần như tài liệu, video, hình ảnh, slides, và được kiến trúc theo một lộ trình nhất định. Thông tin các khoá học được sinh từ các tệp markdown như đối với bài viết.

Thông tin các khoá học đang được triển khai.

Thông tin các khoá học đang được triển khai.

Thông tin một khoá học tại NeuralVN.

Thông tin một khoá học tại NeuralVN.

Giao diện học tập của NeuralVN được xây dựng như hình dưới. Bên trái màn hình, các tài liệu, hình ảnh, video và slides sẽ được hiển thị (kết xuất từ markdown). Bên phải màn hình là phần lựa chọn các bài học và theo dõi quá trình học của mỗi học viên. Quá trình học này sẽ được cập nhật lên cơ sở dữ liệu phía máy chủ.

Untitled

Phần Quiz được thiết kế lồng ghép vào các bài học để kiểm tra mức độ hiểu bài và tăng tương tác của học viên, giúp các bài học thú vị hơn.

Untitled

Đặc biệt, hệ thống NeuralVN được thiết kế để nhấn mạnh vào tính tương tác. Vì vậy, chúng tôi tích hợp việc thực hành ngay tại các bài học để học viên có thể tương tác trực tiếp với code, làm các bài tập thực hành và hiểu bài hơn. Việc thực thi Python được thực hiện nhờ thư viện mang tên Pyodide. Nó sử dụng công nghệ webassembly để đưa Python cùng một số thư viện lên thực thi tại trình duyệt web. Điều này giúp chúng tôi không cần thiết lập một hệ thống chạy mã online trên máy chủ của mình, tiết kiệm được chi phí và hệ thống web cũng có thể mở rộng lớn hơn nhiều.

Hệ thống chấm điểm code tại NeuralVN.

Hệ thống chấm điểm code tại NeuralVN.

Ngoài trình thực thi Python cơ bản như trên, NeuralVN đang nghiên cứu và tích hợp một phiên bản rút gọn của Jupyter notebook, giúp thực thi các thư viện khoa học dữ liệu như Numpy, Scipy, Matplotlib ngay trên web. Chúng tôi tin rằng, công nghệ này sẽ mở ra những tiềm năng mới cho việc xây dựng các khoá học tương tác và phân phối chúng đến nhiều người hơn.

Phiên bản rút gọn của JupterLab trên NeuralVN.

Phiên bản rút gọn của JupterLab trên NeuralVN.

5. Triển khai

Trang web NeuralVN được triển khai trên Vercel. Toàn bộ quá trình triển khai diễn ra tự động sau mỗi commit lên nhánh main. Việc quản lý các phiên bản triển khai được thực hiện dễ dàng qua Dashboard của Vercel.

6. Lời kết

Toàn bộ quá trình xây dựng phiên bản đầu tiên cho website được thực hiện bởi Việt Anh trong khoảng một tháng, tận dụng những thời gian rảnh rỗi buổi tối và cuối tuần (bởi tôi cũng đang có một công việc fulltime). Vì thế, còn khá nhiều phần chưa thực sự hoàn thiện và ưng ý. Hiện tại nhóm NeuralVN đã có 6 thành viên, và chúng tôi đã bắt đầu xây dựng những nội dung đầu tiên cho trang web của mình. Chúng tôi hi vọng trong thời gian sắp tới, nhóm chúng tôi có thể xây dựng và chia sẻ các giá trị cho cộng đồng thông qua website này, đồng thời từng bước nâng cấp NeuralVN để phục vụ nhiều người dùng hơn.

Subscribe Fanpage NeuralVN để cập nhật những thông tin mới nhất về AI, lập trình và sự kiện của chúng tôi!