Back to blog

Local Dev vs Production: Những Gì Dev Mới Chưa Thấy

web developmentdevopsdeploymentbackenddocker
Local Dev vs Production: Những Gì Dev Mới Chưa Thấy

Bạn chạy npm run dev, mở localhost:3000, mọi thứ chạy ngon. Trang load tức thì. API trả về trong 2 ms. Không lỗi gì cả. Ship thôi, đúng không?

Rồi bạn deploy lên production và mọi thứ bắt đầu vỡ theo những cách bạn không bao giờ ngờ tới. API call mà local mất 2 ms giờ thành 200 ms. Ảnh load bình thường giờ trả về 403. Database connection tự dưng drop. User report bug mà bạn không reproduce được.

Bài này sẽ map ra mọi khác biệt lớn giữa local dev environment và một production setup thực tế. Không phải để doạ bạn — mà để bạn biết trước chuyện gì sẽ đến và chuẩn bị cho nó.

Bức Tranh Toàn Cảnh

Local dev là một máy tự nói chuyện với chính nó. Production là một distributed system với CDN, load balancer, nhiều server, managed database, và user connect từ mọi điều kiện network khác nhau.


1. Networking

Local

  • Mọi thứ chạy trên localhost — zero network latency
  • Không cần DNS resolution
  • Không firewall, proxy, hay NAT giữa frontend và backend
  • HTTP hoạt động bình thường (không cần HTTPS)
  • CORS không thành vấn đề khi mọi thứ cùng origin

Production

  • User cách bạn 50–500 ms tùy vị trí địa lý
  • DNS resolution thêm 20–100 ms cho request đầu tiên
  • Nhiều network hop: CDN → load balancer → server → database
  • HTTPS là bắt buộc (TLS handshake thêm ~50 ms)
  • CORS phải config đúng không thì browser block request
  • Firewall và security group giới hạn port/IP nào được kết nối

Cái khiến dev mới bất ngờ

API của bạn trả về trong 2 ms ở local giờ mất 150 ms vì user ở Việt Nam, server ở us-east-1, và có TLS handshake, DNS lookup, cùng ba network hop giữa họ.


2. Security

Local

  • Không cần authentication để access dev server
  • API key hardcode trong .env.local — không sao, chỉ mình bạn thấy
  • Không rate limiting
  • Không input validation? Không vấn đề gì, bạn là user duy nhất
  • HTTP ở mọi nơi
  • Không CSP header, không HSTS, không security header nào cả

Production

  • HTTPS với TLS certificate hợp lệ (Let's Encrypt, Cloudflare, v.v.)
  • Secret lưu trong environment variable hoặc vault (không bao giờ trong code)
  • Rate limiting trên tất cả public endpoint
  • Input validation và sanitization trên mọi user-facing endpoint
  • CORS, CSP, HSTS, X-Frame-Options, và các security header khác
  • DDoS protection (Cloudflare, AWS Shield)
  • Authentication và authorization trên mọi protected route
  • SQL injection và XSS protection

Khoảng cách

AspectLocalProduction
HTTPSKhông cầnBắt buộc
SecretFile .env.localVault / env var trên platform
Rate limitingKhông cóPer-IP, per-user, per-endpoint
Input validationTuỳ thíchCritical
Security headerKhông cóĐầy đủ
AuthThường bỏ quaMọi protected route
DDoS protectionKhông cóCDN + WAF

3. Database

Local

  • File SQLite hoặc một Postgres container
  • Một connection, một user (bạn)
  • Không cần connection pooling
  • Migration chạy thủ công: npx prisma migrate dev
  • Data dùng xong bỏ — drop rồi tạo lại thoải mái
  • Không cần backup
  • Full admin access

Production

  • Managed database (AWS RDS, Neon, PlanetScale, Supabase)
  • Hàng trăm hoặc hàng nghìn concurrent connection
  • Connection pooling là thiết yếu (PgBouncer, Prisma connection pool)
  • Migration phải backward-compatible và zero-downtime
  • Data là thứ giá trị nhất — mất data là mất business
  • Automated backup, point-in-time recovery
  • Principle of least privilege: app user chỉ có đúng permission cần thiết

Cái gì sẽ vỡ

Lỗi database production kinh điển đầu tiên: FATAL: too many clients already. Postgres ở local không bao giờ gặp vấn đề connection limit vì bạn là client duy nhất. Ở production, mỗi serverless function instance mở connection riêng.


4. Environment Variable & Configuration

Local

# .env.local — đơn giản và phẳng
DATABASE_URL=postgresql://postgres:password@localhost:5432/myapp
API_KEY=sk-test-1234567890
NEXT_PUBLIC_API_URL=http://localhost:3000/api

Production

# Secret được quản lý bởi platform (Vercel, AWS, v.v.)
DATABASE_URL=postgresql://user:****@prod-db.us-east-1.rds.amazonaws.com:5432/myapp?sslmode=require
API_KEY=sk-live-**********  # Rotate hàng quý
NEXT_PUBLIC_API_URL=https://chanhle.dev/api
 
# Biến chỉ có ở production
SENTRY_DSN=https://****@sentry.io/123456
REDIS_URL=redis://****@cache.us-east-1.amazonaws.com:6379
CDN_URL=https://cdn.chanhle.dev

Những cái bẫy

  • Quên set env var ở production → app crash khi deploy
  • Dùng URL localhost trong production config → API call fail im lặng
  • Expose secret bằng cách prefix NEXT_PUBLIC_ → ai cũng thấy trên browser
  • Tên env var khác nhau giữa các environment → chạy ở local, vỡ ở CI

5. Error Handling & Logging

Local

  • Error hiện trong terminal với full stack trace
  • React error overlay hiện đúng line number
  • console.log khắp nơi — nó ngay trong terminal của bạn
  • Unhandled promise rejection crash process (rồi bạn restart lại)

Production

  • User thấy trang "Something went wrong" chung chung
  • Stack trace đi vào logging service (Sentry, Datadog, LogRocket)
  • console.log đi đâu không biết (hoặc flood CloudWatch tốn tiền)
  • Unhandled error phải được catch nếu không crash container/function
  • Bạn cần structured logging với request ID để trace issue

Nó trông như thế nào

Debug ở local:

Error: Cannot read properties of undefined (reading 'email')
    at getUserProfile (/Users/you/app/lib/users.ts:42:15)
    at handler (/Users/you/app/api/profile/route.ts:8:20)

Bạn thấy file, line number, và chính xác vấn đề. Fix trong 30 giây.

Debug ở production:

User report: "Em click vào profile mà thấy màn hình trắng"

Bạn check Sentry. Không có gì. Check server log — thấy 500 error ở /api/profile nhưng không có stack trace vì error handling nuốt mất. Bạn thêm logging. Redeploy. Đợi nó xảy ra lại. Check log. Email field của user là null vì họ sign up qua social provider mà không trả về email. Bạn chưa bao giờ test path đó.


6. Build & Deployment

Local

npm run dev     # Hot reload, fast refresh, source map
# Sửa file? Thấy kết quả trong <1 giây

Production

  • Code đi qua CI/CD pipeline: lint → type check → test → build → deploy
  • Build mất 2–10 phút (không phải 1 giây)
  • Phải có khả năng rollback nếu có gì sai
  • Zero-downtime deployment (rolling update, blue-green, canary)
  • Build artifact được optimize, minify, và tree-shake

Những thứ chỉ vỡ ở production build

  • Dynamic import chạy được ở dev nhưng fail sau khi tree-shake
  • Environment variable thiếu lúc build time
  • CSS nhìn đẹp ở dev nhưng vỡ sau khi minify/purge
  • API route chạy ở dev server nhưng không chạy trong serverless function (file system access, long-running process)

7. Scaling

Local

  • Một user: bạn
  • Một process
  • Một CPU core (có thể)
  • Một database connection
  • Nếu nó chậm, bạn đợi

Production

  • Hàng trăm đến hàng triệu concurrent user
  • Nhiều server instance đằng sau load balancer
  • Horizontal scaling: thêm server khi traffic tăng
  • Vertical scaling: máy mạnh hơn cho workload nặng hơn
  • Auto-scaling dựa trên CPU/memory/request count
  • Caching layer: CDN → Redis → application cache → database

Scaling stack

localhost:3000 của bạn không cần bất kỳ thứ gì ở trên. Nhưng ở production, nếu bạn không cache, không dùng CDN, và chạy single server — app sẽ sập ngay lần đầu có traffic thật.


8. Monitoring & Observability

Local

  • Bạn chính là monitoring. Bạn thấy mọi error, mọi log, mọi response chậm.
  • Task Manager / Activity Monitor cho resource usage.
  • Hết.

Production

  • Uptime monitoring: App có đang respond không? (Pingdom, UptimeRobot)
  • Error tracking: Đang có error gì? (Sentry, Bugsnag)
  • Application Performance Monitoring: Request nhanh cỡ nào? (Datadog, New Relic)
  • Log aggregation: Chuyện gì đã xảy ra? (ELK stack, CloudWatch, Grafana Loki)
  • Infrastructure monitoring: CPU, memory, disk, network (Prometheus + Grafana)
  • Alerting: PagerDuty, Opsgenie — gọi ai đó dậy lúc 3 giờ sáng nếu app sập

Bạn không fix được thứ bạn không nhìn thấy

Ở local, bạn thấy mọi thứ. Ở production, nếu bạn không setup logging và monitoring, bạn đang bay mù. User sẽ tìm thấy bug trước bạn.


9. Static Asset & File Storage

Local

// Đọc/ghi file vào filesystem bình thường
import fs from 'fs';
fs.writeFileSync('./uploads/avatar.png', buffer);

Production

  • Local filesystem không persist trong serverless/container environment
  • Dùng object storage: AWS S3, Cloudflare R2, Google Cloud Storage
  • Serve qua CDN cho performance toàn cầu
  • Image optimization: resize, compress, convert sang WebP on the fly (Cloudinary, Vercel Image Optimization)
  • File size limit và virus scanning khi upload

Sai lầm kinh điển

// Chạy hoàn hảo ở local
app.post('/upload', (req, res) => {
  fs.writeFileSync(`./public/uploads/${filename}`, file.buffer);
  res.json({ url: `/uploads/${filename}` });
});
 
// Ở production (serverless):
// ❌ File lưu vào filesystem của container
// ❌ Request tiếp theo có thể hit container khác
// ❌ Container restart = file mất vĩnh viễn

10. Bảng So Sánh Tổng Hợp

AspectLocal DevProduction
URLlocalhost:3000yourdomain.com
HTTPSKhôngCó (bắt buộc)
User1 (bạn)Hàng trăm đến hàng triệu
Latency~0 ms50–500 ms
DatabaseFile local/containerManaged, pooled, replicated
Env var.env.localPlatform secret / vault
ErrorFull stack trace trong terminalLogging service (Sentry, v.v.)
Deploymentnpm run devCI/CD pipeline (5–10 phút)
ScalingSingle processAuto-scaling, load balanced
FileLocal filesystemObject storage (S3, R2)
MonitoringMắt bạnDatadog, Grafana, Sentry
CachingKhông cầnCDN + Redis + app cache
SecurityTối thiểuFull stack (WAF, rate limit, CSP)
DowntimeGập laptop lại≤ 99.9% SLA expected
RollbackCtrl+ZBlue-green / canary deploy

Cách Thu Hẹp Khoảng Cách

Bạn không cần setup full production stack ngay ngày đầu. Nhưng bạn có thể bắt đầu thu hẹp gap sớm:

Level 1: Docker (Tuần 1)

Chạy app trong Docker ở local. Cái này bắt được bug "works on my machine".

docker compose up -d

Level 2: Environment Parity (Tuần 2)

Dùng database thật (Postgres, không phải SQLite). Dùng environment variable cho mọi config. Đừng bao giờ hardcode URL.

Level 3: CI/CD (Tháng 1)

Setup GitHub Actions pipeline chạy lint, test, và build mỗi lần push.

# .github/workflows/ci.yml
name: CI
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run lint
      - run: npm run build
      - run: npm test

Level 4: Observability (Tháng 2)

Thêm Sentry cho error tracking. Thêm basic health check. Setup uptime monitoring.

Level 5: Production Mindset (Liên tục)

  • Assume mọi input đều là malicious
  • Assume mọi network call đều có thể fail
  • Assume mọi server đều có thể restart bất kỳ lúc nào
  • Assume database sẽ hết connection
  • Assume disk sẽ hết dung lượng

Key Takeaway

Gap giữa localhost và production không phải một hố sâu duy nhất — mà là hàng chục gap nhỏ hơn trải khắp networking, security, scaling, database, deployment, monitoring, và nhiều thứ khác.

Bạn không cần master tất cả trước khi ship project đầu tiên. Nhưng bạn cần biết chúng tồn tại để không bị bất ngờ khi mọi thứ vỡ theo cách không thể reproduce ở local.

Bắt đầu với Docker và environment parity. Thêm CI/CD sớm. Layer thêm monitoring và security khi app phát triển. Mỗi production incident bạn sống sót qua đều khiến bạn trở thành developer tốt hơn.

Những developer phát triển nhanh nhất không phải là người viết code cleverest ở local — mà là người hiểu chuyện gì xảy ra sau git push.

📬 Đăng ký nhận bản tin

Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.

💬 Comments

Đăng nhập để bình luận

Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.