Bài 34.1: Deploying Next.js-TypeScript apps

Chào mừng bạn quay trở lại với chuỗi bài viết về phát triển web front-end! Sau hành trình xây dựng những ứng dụng Next.js mạnh mẽ với sự hỗ trợ của TypeScript, giờ là lúc biến thành quả của chúng ta thành một sản phẩm sống, có thể truy cập được từ bất kỳ đâu trên thế giới. Bước cuối cùng đầy kỳ diệu này chính là triển khai ứng dụng (deployment).

Triển khai ứng dụng Next.js không chỉ đơn thuần là "đặt file lên server". Next.js là một framework full-stack với khả năng Server-Side Rendering (SSR), Static Site Generation (SSG), và API Routes chạy dưới dạng Serverless Functions hoặc trên một Node.js server truyền thống. Do đó, quy trình deployment cần đảm bảo rằng tất cả các khía cạnh này đều hoạt động chính xác. Việc sử dụng TypeScript trong dự án của chúng ta cũng mang lại những lợi ích nhất định trong quá trình này, chủ yếu tập trung vào giai đoạn build (xây dựng) trước khi triển khai.

Hãy cùng khám phá các phương pháp phổ biến để đưa ứng dụng Next.js-TypeScript của bạn lên mạng!

Quá trình "Build" - Nền tảng của Deployment

Trước khi nói về việc đặt ứng dụng ở đâu, chúng ta cần hiểu quá trình chuẩn bị ứng dụng. Với các ứng dụng front-end hiện đại như Next.js, đặc biệt khi sử dụng TypeScript, chúng ta không chỉ sao chép mã nguồn lên server. Thay vào đó, chúng ta thực hiện quá trình build (xây dựng).

Lệnh next build là trái tim của giai đoạn chuẩn bị này. Lệnh này sẽ:

  1. Kiểm tra mã nguồn của bạn, bao gồm cả việc kiểm tra kiểu (type checking) nếu bạn dùng TypeScript. Nếu có lỗi TypeScript, quá trình build sẽ dừng lại, giúp bạn phát hiện sớm các vấn đề tiềm ẩn.
  2. Biên dịch mã TypeScript/JavaScript, React, và các tài nguyên khác (CSS, hình ảnh...).
  3. Tối ưu hóa mã nguồn (minification, code splitting...).
  4. Tạo ra các file đầu ra sẵn sàng để phục vụ (HTML tĩnh, các chunk JavaScript, các API routes biên dịch...).

Output của quá trình build thường nằm trong thư mục .next. Đây chính là những gì chúng ta sẽ cần để triển khai ứng dụng.

Các Nền tảng Triển khai Phổ biến

Có nhiều lựa chọn để triển khai ứng dụng Next.js, từ các dịch vụ chuyên biệt cho front-end đến việc tự quản lý server. Mỗi lựa chọn có ưu và nhược điểm riêng.

1. Triển khai với Vercel

Vercel là nền tảng được tạo ra bởi chính đội ngũ phát triển Next.js. Do đó, nó cung cấp sự tích hợp sâu sắc nhất và là lựa chọn đơn giản nhất trong hầu hết các trường hợp.

Tại sao chọn Vercel?

  • Tích hợp liền mạch: Tự động nhận diện dự án Next.js, cấu hình build, và deploy mọi tính năng (SSR, SSG, API Routes dưới dạng Serverless Functions, Edge Functions) mà không cần cấu hình phức tạp.
  • Hiệu suất cao: Sử dụng mạng lưới Edge Network toàn cầu để phục vụ nội dung nhanh chóng.
  • Tự động: Tự động deploy mỗi khi bạn push code lên Git branch đã kết nối. Cung cấp các bản xem trước (Preview Deployments) cho mỗi pull request.
  • Miễn phí: Có gói Free tier rất hào phóng cho các dự án cá nhân và phi thương mại.

Các bước cơ bản:

  1. Kết nối Git Repository: Đăng nhập vào Vercel bằng tài khoản Git (GitHub, GitLab, Bitbucket). Chọn repository chứa dự án Next.js của bạn.
  2. Cấu hình dự án: Vercel sẽ tự động phát hiện Next.js. Bạn chỉ cần xác nhận các cài đặt cơ bản như root directory (thường là /), build command (next build), và output directory (.next).
  3. Cấu hình biến môi trường (Environment Variables): Nếu ứng dụng của bạn sử dụng biến môi trường (ví dụ: kết nối database, khóa API), bạn có thể thêm chúng trực tiếp trong dashboard của Vercel.

Ví dụ, nếu bạn có file .env.local trong dự án:

# .env.local
DB_CONNECTION_STRING=your_db_connection_string
API_KEY=your_secret_api_key

Bạn sẽ cần thêm các biến DB_CONNECTION_STRINGAPI_KEY vào cài đặt môi trường trong dashboard Vercel. Vercel sẽ cung cấp các biến này cho quá trình build và runtime của ứng dụng.

// Trong mã nguồn Next.js (ví dụ: trong getStaticProps, getServerSideProps, API Routes)
// Các biến môi trường chỉ có trên server (runtime) hoặc trong quá trình build
const dbConnectionString = process.env.DB_CONNECTION_STRING;

// Đối với biến cần dùng ở client-side (phải bắt đầu bằng NEXT_PUBLIC_)
// Ví dụ trong .env.local: NEXT_PUBLIC_ANALYTICS_ID=abcde12345
// Bạn có thể truy cập: process.env.NEXT_PUBLIC_ANALYTICS_ID

Giải thích: Next.js hỗ trợ biến môi trường. Biến không bắt đầu bằng NEXT_PUBLIC_ chỉ có sẵn ở môi trường Node.js (server-side/API Routes/build time), còn biến có tiền tố NEXT_PUBLIC_ sẽ được bundle vào mã client-side (chú ý: không dùng cho các thông tin nhạy cảm!). Vercel cho phép bạn cấu hình các biến này một cách an toàn qua giao diện web hoặc Vercel CLI.

Sau khi cấu hình, Vercel sẽ tự động build và deploy ứng dụng của bạn. Mọi push tiếp theo lên branch chính sẽ kích hoạt một deployment mới.

2. Triển khai với Netlify

Netlify là một nền tảng hosting phổ biến khác cho các ứng dụng front-end tĩnh và dynamic. Nó cũng cung cấp trải nghiệm deployment rất tốt cho Next.js.

Tại sao chọn Netlify?

  • Dễ sử dụng: Tương tự Vercel, Netlify tích hợp tốt với Git và tự động hóa quy trình build/deploy.
  • Mạng lưới CDN mạnh mẽ: Phục vụ các file tĩnh và nội dung SSG rất nhanh.
  • Hỗ trợ Serverless Functions: Netlify Functions cho phép chạy API Routes.
  • Add-ons hữu ích: Forms, Identity, Split Testing...

Các bước cơ bản:

  1. Kết nối Git Repository: Đăng nhập vào Netlify bằng tài khoản Git và chọn repo của bạn.
  2. Cấu hình Build: Netlify cũng thường tự động nhận diện Next.js. Bạn cần xác nhận:
    • Build command: next build
    • Publish directory: Mặc định Next.js build ra .next. Tuy nhiên, để Netlify hỗ trợ đầy đủ SSR/API Routes, bạn cần sử dụng @netlify/plugin-nextjs. Plugin này sẽ xử lý output của next build và cấu hình Netlify Functions phù hợp. Khi dùng plugin, publish directory vẫn có thể là .next hoặc plugin sẽ chỉ định lại.

Để sử dụng plugin, bạn cần tạo file netlify.toml ở thư mục gốc của dự án:

# netlify.toml
[build]
  command = "next build"
  publish = ".next" # hoặc thư mục plugin chỉ định, thường không cần khai báo nếu dùng plugin

[[plugins]]
  package = "@netlify/plugin-nextjs"

Giải thích: File netlify.toml dùng để cấu hình Netlify cho dự án của bạn. Mục [build] định nghĩa lệnh build và thư mục chứa output. Mục [[plugins]] khai báo các plugin Netlify cần sử dụng. Plugin @netlify/plugin-nextjsquan trọng để Netlify hiểu và chạy đúng các tính năng dynamic của Next.js (SSR, API Routes) bằng cách chuyển chúng thành Netlify Functions.

  1. Cấu hình biến môi trường: Tương tự Vercel, bạn cấu hình trong dashboard Netlify hoặc dùng file .env trong dự án (với Netlify Build).

Netlify cũng sẽ tự động build và deploy sau khi bạn push code, cung cấp các tính năng preview và quản lý branch.

3. Triển khai lên Node.js Server tự quản lý

Nếu bạn cần kiểm soát hoàn toàn môi trường server, hoặc triển khai lên các nền tảng IaaS (Infrastructure as a Service) như AWS EC2, DigitalOcean Droplets, Azure VMs, bạn có thể chạy ứng dụng Next.js như một ứng dụng Node.js thông thường.

Tại sao chọn Self-hosting?

  • Kiểm soát hoàn toàn: Bạn quản lý hệ điều hành, cấu hình server, phần mềm, bảo mật...
  • Phù hợp với hạ tầng hiện có: Nếu công ty bạn đã có sẵn server hoặc cloud infrastructure.
  • Linh hoạt: Có thể tích hợp sâu với các dịch vụ nội bộ khác.

Nhược điểm:

  • Phức tạp hơn: Bạn phải tự lo mọi thứ: cài đặt Node.js, cấu hình server, quản lý tiến trình (process management), load balancing, SSL, giám sát...
  • Tốn kém thời gian và chi phí: Đòi hỏi kiến thức về quản trị server và bảo trì liên tục.

Các bước cơ bản:

  1. Build ứng dụng: Chạy lệnh next build trên máy local hoặc trên server.

    npm run build
    # hoặc
    yarn build
    

    Lệnh này sẽ tạo ra thư mục .next.

  2. Sao chép file: Copy thư mục .next, thư mục public, package.json, package-lock.json (hoặc yarn.lock), next.config.js (nếu có), và bất kỳ file server custom nào (nếu bạn không dùng server mặc định của Next.js) lên server của bạn.

  3. Cài đặt dependencies: Trên server, di chuyển vào thư mục dự án và cài đặt các gói dependencies chỉ cho môi trường production.

    npm install --production
    # hoặc
    yarn install --production --frozen-lockfile
    
  4. Chạy ứng dụng: Next.js cung cấp lệnh next start để chạy ứng dụng ở chế độ production sau khi đã build.

    npm start
    # hoặc
    yarn start
    

    Lệnh npm start thường được cấu hình sẵn trong package.json:

    // package.json
    "scripts": {
      "dev": "next dev",
      "build": "next build",
      "start": "next start", // <-- Đây  lệnh chạy production server
      "lint": "next lint"
    },
    

Giải thích: Lệnh next start sẽ khởi động một HTTP server bằng Node.js, sử dụng các file đã build trong thư mục .next để phục vụ ứng dụng. Nó xử lý cả các request SSR, API Routes và các file tĩnh.

  1. Quản lý tiến trình (Process Management): Lệnh next start sẽ dừng lại nếu server khởi động lại hoặc gặp lỗi. Trong môi trường production, bạn cần sử dụng một công cụ quản lý tiến trình như PM2 hoặc Forever để đảm bảo ứng dụng luôn chạy và tự khởi động lại khi cần thiết.

    Ví dụ với PM2:

    # Cài đặt PM2 toàn cục
    npm install -g pm2
    
    # Khởi chạy ứng dụng Next.js với PM2
    pm2 start npm --name "my-next-app" -- start
    # Hoặc nếu dùng yarn:
    # pm2 start yarn --name "my-next-app" -- start
    
    # Lưu cấu hình hiện tại để PM2 tự khởi động lại khi server boot
    pm2 save
    
    # Khởi động PM2 khi server boot (lệnh này được tạo ra bởi pm2 save)
    # pm2 startup
    

    Giải thích: PM2 giữ cho tiến trình Node.js của bạn chạy ngầm, tự động khởi động lại nếu nó crash, và có thể quản lý nhiều ứng dụng cùng lúc. pm2 start npm -- start (hoặc pm2 start yarn -- start) nói với PM2 chạy lệnh npm start (hoặc yarn start) trong thư mục hiện tại.

  2. Reverse Proxy: Để xử lý các tác vụ như SSL/HTTPS, nén Gzip, caching, và phục vụ các file tĩnh trực tiếp (hiệu quả hơn Node.js), bạn nên đặt một reverse proxy phía trước ứng dụng Node.js của mình. Các lựa chọn phổ biến là Nginx hoặc Caddy. Ứng dụng Node.js thường chạy trên một port nội bộ (mặc định 3000 với next start), và reverse proxy sẽ lắng nghe ở port 80/443, chuyển tiếp request đến ứng dụng Node.js.

    Cấu hình Nginx (ví dụ):

    server {
        listen 80;
        server_name your-domain.com;
    
        # Redirect HTTP to HTTPS
        return 301 https://$host$request_uri;
    }
    
    server {
        listen 443 ssl http2;
        server_name your-domain.com;
    
        # Cấu hình SSL (certbot hoặc tự cấu hình)
        ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
        # ... các cấu hình SSL khác
    
        location / {
            proxy_pass http://localhost:3000; # Port ứng dụng Next.js đang chạy
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            # ... các cấu hình proxy khác
        }
    
        # Có thể cấu hình để Nginx tự phục vụ các file tĩnh trong thư mục public/_next/static
        location /_next/static {
             alias /path/to/your/app/.next/static;
             expires 1y;
             access_log off;
             add_header Cache-Control "public, max-age=31536000, immutable";
        }
    
        location /static { # Nếu bạn có thư mục static trong public
             alias /path/to/your/app/public/static;
             expires 1y;
             access_log off;
             add_header Cache-Control "public, max-age=31536000, immutable";
        }
    }
    

    Giải thích: Nginx lắng nghe trên port 80 và 443. Nó xử lý SSL, sau đó chuyển tiếp (proxy) các request đến ứng dụng Next.js đang chạy trên localhost:3000. Cấu hình cho thư mục _next/staticpublic/static giúp Nginx phục vụ trực tiếp các file này với cache mạnh mẽ, giảm tải cho Node.js server.

Quy trình self-hosting yêu cầu kiến thức về Linux server, networking, và quản lý tiến trình. Nó phù hợp khi bạn cần kiểm soát cao nhất hoặc tích hợp sâu vào hạ tầng hiện có.

4. Triển khai với Docker

Docker cung cấp một cách đóng gói ứng dụng và môi trường của nó thành các "container" di động. Điều này giúp đảm bảo ứng dụng chạy nhất quán bất kể môi trường triển khai là gì (server cá nhân, cloud VM, Kubernetes...).

Tại sao chọn Docker?

  • Nhất quán môi trường: Loại bỏ vấn đề "nó chạy được trên máy tôi".
  • Cô lập: Container chạy độc lập, không ảnh hưởng đến hệ thống host hoặc các container khác.
  • Khả năng mở rộng: Dễ dàng scale ứng dụng bằng cách chạy nhiều instance của container.
  • Quản lý đơn giản: Triển khai và quản lý ứng dụng qua các lệnh Docker hoặc nền tảng điều phối (Orchestration) như Kubernetes.

Các bước cơ bản:

  1. Viết Dockerfile: File này chứa các chỉ dẫn để xây dựng (build) image Docker cho ứng dụng của bạn.

    # Dockerfile
    
    # Giai đoạn Build: Sử dụng image Node.js để build ứng dụng
    FROM node:18-alpine AS builder
    
    WORKDIR /app
    
    # Copy package.json và lock file để tận dụng cache Docker layer
    COPY package.json ./
    COPY yarn.lock ./ # hoặc package-lock.json nếu dùng npm
    
    # Cài đặt dependencies
    RUN yarn install --frozen-lockfile --production=false # hoặc npm ci
    
    # Copy toàn bộ mã nguồn
    COPY . .
    
    # Build ứng dụng Next.js (bao gồm biên dịch TypeScript)
    RUN yarn build # hoặc npm run build
    
    # Giai đoạn Run: Sử dụng image Node.js nhỏ hơn để chạy ứng dụng đã build
    FROM node:18-alpine AS runner
    
    WORKDIR /app
    
    # Copy file package.json (chỉ cần script start) và build output từ giai đoạn builder
    COPY --from=builder /app/package.json ./
    COPY --from=builder /app/.next ./.next
    COPY --from=builder /app/public ./public
    COPY --from=builder /app/node_modules ./node_modules # Copy dependencies production
    
    # Biến môi trường (nếu cần, có thể truyền vào lúc run container)
    # ENV NODE_ENV production
    # ENV PORT 3000
    
    EXPOSE 3000 # Next.js production server chạy trên port 3000 mặc định
    
    # Lệnh để chạy ứng dụng khi container khởi động
    CMD ["yarn", "start"] # hoặc ["npm", "start"]
    

    Giải thích: Dockerfile trên sử dụng Multi-stage build.

    • Giai đoạn builder dùng image Node.js đầy đủ để cài đặt dependencies và chạy next build.
    • Giai đoạn runner dùng một image Node.js nhỏ gọn hơn (alpine) chỉ để chạy output đã build. Chúng ta chỉ copy những gì cần thiết (.next, public, node_modules production, package.json). Điều này giúp image Docker cuối cùng có kích thước nhỏ hơn, an toàn hơn.
    • WORKDIR /app: Đặt thư mục làm việc bên trong container.
    • COPY: Sao chép file từ host vào container. --from=builder chỉ định sao chép từ giai đoạn builder.
    • RUN: Thực thi lệnh trong container.
    • EXPOSE: Thông báo container lắng nghe trên port nào (không thực sự publish port ra ngoài).
    • CMD: Lệnh mặc định chạy khi container khởi động.
  2. Build image Docker: Từ thư mục chứa Dockerfile và mã nguồn dự án, chạy lệnh build.

    docker build -t my-next-app .
    

    Giải thích: Lệnh này xây dựng image Docker dựa trên Dockerfile trong thư mục hiện tại (.). -t my-next-app đặt tên cho image là my-next-app.

  3. Chạy container: Chạy một instance của image Docker đã build.

    docker run -p 80:3000 my-next-app
    

    Giải thích: Lệnh này tạo và chạy một container từ image my-next-app. -p 80:3000 ánh xạ port 80 trên host ra port 3000 bên trong container (nơi ứng dụng Next.js đang chạy).

Triển khai với Docker rất linh hoạt. Bạn có thể đẩy image lên một Docker Registry (như Docker Hub, AWS ECR, Google Container Registry) và triển khai trên các dịch vụ hỗ trợ container (ECS, EKS, GKE, Azure Kubernetes Service, DigitalOcean Kubernetes...).

TypeScript có ảnh hưởng gì đến Deployment?

Điểm mấu chốt cần nhớ là TypeScript không ảnh hưởng trực tiếp đến quy trình triển khai trên các nền tảng hosting như Vercel, Netlify, hay khi chạy bằng next start.

Lý do là quá trình next build đã bao gồm việc biên dịch (compiling) mã TypeScript sang JavaScript, kiểm tra kiểu, và tối ưu hóa. Kết quả cuối cùng của next build là các file JavaScript, HTML, CSS, v.v., mà không còn mã TypeScript gốc nữa.

Các nền tảng deployment chỉ làm việc với output của next build (thư mục .nextpublic). Họ không cần cài đặt TypeScript hay chạy trình biên dịch TypeScript (tsc).

Lợi ích của TypeScript trong bối cảnh Deployment:

  • Phát hiện lỗi sớm: Quá trình build sẽ thất bại nếu có lỗi kiểu trong mã TypeScript. Điều này giúp bạn bắt được nhiều bug trước khi deploy, thay vì phát hiện chúng ở môi trường production. Đây là một lợi thế đáng kể so với JavaScript thuần.
  • Độ tin cậy cao hơn: Mã nguồn được kiểm tra kiểu giúp giảm khả năng xảy ra các lỗi runtime liên quan đến kiểu dữ liệu không mong muốn sau khi deploy.

Tóm lại, việc sử dụng TypeScript làm cho giai đoạn build an toàn và đáng tin cậy hơn, nhưng bản thân quy trình deployment (đặt file lên server và chạy) không khác biệt so với một ứng dụng Next.js viết bằng JavaScript thuần.

Những lưu ý quan trọng và Best Practices

  • Biến môi trường: Luôn luôn sử dụng biến môi trường để lưu trữ thông tin cấu hình nhạy cảm (API keys, database credentials) hoặc các giá trị thay đổi giữa các môi trường (dev, staging, production). Không bao giờ commit các thông tin nhạy cảm trực tiếp vào mã nguồn hoặc file .env mà không đưa vào .gitignore.
  • CI/CD (Continuous Integration/Continuous Deployment): Tự động hóa quy trình build, test và deploy mỗi khi có sự thay đổi mã nguồn. Các nền tảng như Vercel và Netlify có CI/CD tích hợp sẵn. Với self-hosting hoặc Docker, bạn có thể dùng GitHub Actions, GitLab CI, Jenkins, CircleCI...
  • Tối ưu hóa Build: Thời gian build ảnh hưởng đến tốc độ của quy trình CI/CD. Đảm bảo bạn sử dụng cache dependency hiệu quả (ví dụ: trong Dockerfile hoặc cấu hình CI).
  • Giám sát (Monitoring): Sau khi deploy, hãy thiết lập công cụ giám sát để theo dõi hiệu suất ứng dụng, lỗi runtime, và lưu lượng truy cập. Vercel và Netlify cung cấp một số tính năng monitoring cơ bản, hoặc bạn có thể tích hợp với các dịch vụ bên thứ ba như Sentry, Datadog, New Relic...
  • Custom Domain và SSL: Hầu hết các nền tảng hosting (Vercel, Netlify) đều cung cấp cách dễ dàng để cấu hình tên miền tùy chỉnh và chứng chỉ SSL/HTTPS miễn phí (thường qua Let's Encrypt). Với self-hosting, bạn cần tự cấu hình SSL, thường là sử dụng Certbot để tích hợp Let's Encrypt với Nginx/Caddy.
  • Cache: Hiểu cách Next.js xử lý cache (ví dụ: cache cho các trang SSG, cache cho API responses) và cách nền tảng deployment/reverse proxy của bạn xử lý caching (cache ở CDN, cache trình duyệt).

Comments

There are no comments at the moment.