تصویر مقاله افزایش سرعت لود NextJs در حالت توسعه با داکر

افزایش سرعت لود NextJs در حالت توسعه با داکر

خلاصه:

اگر پروژه‌ای دارید که چند سرویس مختلف با Docker اجرا می‌شوند و یکی از این سرویس‌ها Next.js است، ممکن است در حالت Development با یک مشکل آزاردهنده مواجه شده باشید: تغییرات کد با تأخیر نمایش داده می‌شوند، Fast Refresh کند است یا حتی باز شدن صفحات نسبت به اجرای معمولی Next.js زمان بیشتری می‌برد.

مقاله آموزشی۱ روز پیش11 دقیقه0 نظر
۳ بازدید

اگر پروژه‌ای دارید که چند سرویس مختلف با Docker اجرا می‌شوند و یکی از این سرویس‌ها Next.js است، ممکن است در حالت Development با یک مشکل آزاردهنده مواجه شده باشید: تغییرات کد با تأخیر نمایش داده می‌شوند، Fast Refresh کند است یا حتی باز شدن صفحات نسبت به اجرای معمولی Next.js زمان بیشتری می‌برد.

در این شرایط لزوماً لازم نیست کل محیط توسعه را از Docker خارج کنیم. یکی از راهکارهای ساده این است که سرویس‌هایی مثل دیتابیس، Redis و Backend همچنان داخل Docker اجرا شوند، اما Next.js را مستقیماً روی سیستم خودمان اجرا کنیم.

در این مقاله می‌بینیم چرا این روش می‌تواند سرعت محیط توسعه را بهتر کند و چطور آن را مرحله‌به‌مرحله پیاده‌سازی کنیم.

چه زمانی این مقاله به درد شما می‌خورد؟

این روش زمانی ارزش امتحان کردن دارد که شرایطی شبیه موارد زیر دارید:

  • پروژه شما با Docker Compose اجرا می‌شود.
  • Next.js هم یکی از سرویس‌های Docker Compose است.
  • سورس پروژه Next.js با Bind Mount به کانتینر متصل شده است.
  • تغییر یک فایل باعث می‌شود Fast Refresh یا Hot Reload با تأخیر انجام شود.
  • اجرای npm run dev خارج از Docker به شکل محسوسی سریع‌تر است.
  • می‌خواهید Backend، دیتابیس، Redis، Celery یا سرویس‌های دیگر همچنان Dockerized باقی بمانند.

هدف این نیست که Docker را از محیط توسعه حذف کنیم. قرار است فقط بخشی که در زمان توسعه بیشترین تعامل را با فایل‌های پروژه دارد، یعنی Next.js، روی سیستم Local اجرا شود.

چرا Next.js داخل Docker ممکن است در Development کند شود؟

در حالت Development، Next.js دائماً با فایل‌های پروژه درگیر است.

وقتی فایلی را تغییر می‌دهید، محیط توسعه باید تغییر را تشخیص دهد، فایل‌های موردنیاز را دوباره پردازش کند و نتیجه جدید را در اختیار مرورگر قرار دهد.

اگر سورس پروژه را با Bind Mount وارد کانتینر کرده باشید، مثلاً:

Plain text
volumes:
  - ./frontend:/app

فایل‌های روی سیستم شما باید در اختیار محیط داخل کانتینر قرار بگیرند.

Docker نیز Bind Mount را دقیقاً برای همین نوع اشتراک فایل بین Host و Container ارائه می‌کند.

اما این ارتباط همیشه بدون هزینه نیست.

این موضوع مخصوصاً هنگام استفاده از Docker Desktop روی macOS و Windows بیشتر اهمیت پیدا می‌کند، چون Docker Desktop کانتینرهای Linux را داخل یک محیط Linux اجرا می‌کند و اشتراک فایل بین فایل‌سیستم Host و این محیط می‌تواند سربار ایجاد کند. خود مستندات Docker نیز اشاره می‌کنند که File Sharing سربار دارد و اشتراک تعداد زیادی فایل می‌تواند باعث افزایش مصرف CPU و کاهش کارایی فایل‌سیستم شود.

در پروژه‌های Next.js این مسئله می‌تواند بیشتر احساس شود، چون هنگام Development تعداد زیادی فایل و Dependency وجود دارد و ابزار توسعه باید تغییرات فایل‌ها را مرتب دنبال کند.

Docker حتی برای پروژه‌های بزرگ قابلیت Synchronized File Shares را ارائه کرده تا عملکرد Bind Mountها را بهتر کند.

بنابراین اگر مشکل اصلی شما مربوط به همین مسیر فایل‌سیستم باشد، یک راه ساده این است:

Next.js را از Docker خارج کنیم، اما باقی سرویس‌ها را داخل Docker نگه داریم.

ساختار جدید محیط Development

فرض کنید پروژه ما تقریباً چنین سرویس‌هایی دارد:

Plain text
PostgreSQL
Redis
Django Backend
Celery Worker
Next.js Frontend

در حالت قبلی همه آن‌ها داخل Docker اجرا می‌شدند.

اما در حالت جدید ساختار را به شکل زیر تغییر می‌دهیم:

Plain text
Docker
├── PostgreSQL
├── Redis
├── Django Backend
└── Celery Worker

Local Machine
└── Next.js

Next.js مستقیماً روی سیستم اجرا می‌شود و از طریق پورت‌هایی که Docker روی Host منتشر کرده است با Backend ارتباط برقرار می‌کند.

معماری Development: Next.js روی سیستم Local و سرویس‌ها داخل Docker

مرحله اول: سرویس Next.js را داخل Docker اجرا نکنید

فرض کنیم فایل Development ما docker-compose.dev.yml است.

به‌جای بالا آوردن همه سرویس‌ها، فقط سرویس‌هایی را اجرا می‌کنیم که می‌خواهیم داخل Docker باقی بمانند:

Bash / Shell
docker compose --env-file .env.prod -f docker-compose.dev.yml up -d postgres redis backend

سپس Celery Worker را نیز اجرا می‌کنیم:

Bash / Shell
docker compose --env-file .env.prod -f docker-compose.dev.yml up -d celery_worker

نکته مهم این است که در این دستورات سرویس Frontend را اجرا نکرده‌ایم.

بنابراین Docker مسئول اجرای PostgreSQL، Redis، Backend و Celery است، اما Next.js را خودمان روی سیستم اجرا خواهیم کرد.

مرحله دوم: مطمئن شوید Backend از Host قابل دسترسی است

Next.js دیگر داخل Docker Network قرار ندارد.

وقتی Frontend و Backend هر دو داخل Docker باشند، ممکن است بتوانیم با نام سرویس به Backend متصل شویم:

Plain text
http://backend:8000

این آدرس داخل شبکه Docker قابل استفاده است، اما Next.js که حالا روی سیستم شما اجرا شده، معمولاً نمی‌تواند از این نام استفاده کند.

در عوض باید پورت Backend روی Host منتشر شده باشد.

برای مثال:

Plain text
services:
  backend:
    ports:
      - "8000:8000"

در این حالت Backend از سیستم شما با آدرس زیر قابل دسترسی است:

Plain text
http://127.0.0.1:8000

قبل از اجرای Next.js بهتر است همین آدرس را در مرورگر یا با ابزارهایی مثل curl بررسی کنید.

برای مثال:

Bash / Shell
curl http://127.0.0.1:8000

اگر پاسخ Backend را دریافت کردید، ارتباط Host با Backend برقرار است.

مرحله سوم: Next.js را روی سیستم Local اجرا کنید

حالا وارد پوشه Frontend شوید:

Bash / Shell
cd frontend

اگر Dependencyهای پروژه هنوز روی سیستم نصب نشده‌اند:

Bash / Shell
npm install

یا اگر پروژه دارای package-lock.json است و می‌خواهید دقیقاً Dependencyهای Lock شده نصب شوند:

Bash / Shell
npm ci

سپس Environment Variableهای موردنیاز پروژه را تنظیم کنید.

در پروژه نمونه ما:

Bash / Shell
export DJANGO_PUBLIC_API_URL=http://127.0.0.1:8000
export NEXT_PUBLIC_BASE_URL=http://localhost:3000
export NEXT_PUBLIC_NEXTJS_API_URL=http://localhost:3000

در نهایت Next.js را اجرا کنید:

Bash / Shell
npm run dev

حالا Frontend معمولاً از آدرس زیر در دسترس است:

Plain text
http://localhost:3000

ولی Backend همچنان داخل Docker و از طریق آدرس زیر در دسترس Next.js است:

Plain text
http://127.0.0.1:8000

بهتر است متغیرها را داخل .env.local قرار دهیم

اجرای چند دستور export در هر بار شروع پروژه کمی خسته‌کننده است.

در Next.js می‌توانیم تنظیمات مربوط به محیط Local را داخل فایل .env.local قرار دهیم.

برای مثال:

Plain text
DJANGO_PUBLIC_API_URL=http://127.0.0.1:8000
NEXT_PUBLIC_BASE_URL=http://localhost:3000
NEXT_PUBLIC_NEXTJS_API_URL=http://localhost:3000

حالا برای اجرای Frontend کافی است:

Bash / Shell
cd frontend
npm run dev

البته نام Environment Variableها کاملاً به ساختار پروژه شما بستگی دارد و موارد بالا فقط براساس پروژه نمونه این مقاله هستند.

یک نکته مهم درباره آدرس سرویس‌ها

یکی از اشتباهات رایج بعد از خارج کردن Next.js از Docker این است که آدرس‌های داخلی Docker را تغییر ندهیم.

برای مثال ممکن است قبلاً چنین مقداری داشته باشید:

Plain text
DJANGO_PUBLIC_API_URL=http://backend:8000

backend نام یک سرویس در Docker Compose است و معمولاً سرویس‌های دیگری که در همان شبکه Docker قرار دارند می‌توانند آن را Resolve کنند.

اما Next.js ما دیگر در آن شبکه نیست.

بنابراین باید از پورتی استفاده کنیم که روی Host منتشر شده است:

Plain text
DJANGO_PUBLIC_API_URL=http://127.0.0.1:8000

این یکی از مهم‌ترین تغییرات هنگام اجرای Frontend خارج از Docker است.

اگر از Windows و WSL استفاده می‌کنید

اگر نمی‌خواهید Next.js را از Docker خارج کنید، یک راهکار دیگر هم ارزش بررسی دارد.

Docker توصیه می‌کند در محیط WSL 2 سورس پروژه‌هایی که با Linux Container به‌صورت Bind Mount استفاده می‌شوند، داخل فایل‌سیستم Linux قرار داشته باشند و نه مسیرهایی مثل /mnt/c.

Docker اعلام می‌کند عملکرد Bind Mount از فایل‌سیستم Linux بهتر است و File Change Eventها نیز در این حالت بهتر در اختیار کانتینر قرار می‌گیرند.

بنابراین این مسیر:

Plain text
/home/user/projects/my-project

برای چنین سناریویی معمولاً انتخاب مناسب‌تری از مسیری شبیه این است:

Plain text
/mnt/c/Users/user/projects/my-project

اگر با همین تغییر مشکل شما حل شد، شاید اصلاً نیازی به خارج کردن Next.js از Docker نداشته باشید.

مزیت این روش چیست؟

با این ساختار همچنان مزایای Docker را برای بخش‌هایی که به آن نیاز دارند حفظ می‌کنیم.

PostgreSQL، Redis، Backend و Celery بدون نیاز به نصب مستقیم روی سیستم اجرا می‌شوند، اما Next.js مستقیماً به فایل‌های پروژه روی سیستم دسترسی دارد.

در نتیجه مسیر Development ساده‌تر می‌شود:

Plain text
ویرایش فایل
    ↓
Next.js روی سیستم Local
    ↓
Fast Refresh
    ↓
مرورگر

و برای درخواست‌های Backend:

Plain text
Next.js
    ↓
127.0.0.1:8000
    ↓
Docker
    ↓
Django Backend

این معماری مخصوص Development است و به این معنی نیست که در Production نیز باید Next.js را خارج از Docker اجرا کنید.

آیا همیشه باید Next.js را از Docker خارج کنیم؟

خیر.

اجرای Next.js داخل Docker به خودی خود اشتباه نیست.

Bind Mountها نیز یکی از روش‌های استاندارد Docker برای توسعه هستند.

اگر سرعت Development مناسب است، دلیلی ندارد ساختار پروژه را تغییر دهید.

این راهکار بیشتر زمانی مفید است که بررسی کرده‌اید گلوگاه محیط Development شما اجرای Next.js همراه با فایل‌های Bind Mount شده داخل Docker است و اجرای npm run dev مستقیماً روی Host تجربه بسیار سریع‌تری ایجاد می‌کند.

همچنین باید در نظر داشته باشید که با اجرای Local، نسخه Node.js نصب‌شده روی سیستم بهتر است با نسخه‌ای که پروژه یا Dockerfile انتظار دارد هماهنگ باشد. ابزارهایی مثل nvm می‌توانند مدیریت نسخه Node.js را ساده‌تر کنند.

جمع‌بندی

اگر Next.js را همراه با کل پروژه داخل Docker اجرا می‌کنید و در زمان Development با کندی Fast Refresh، دیر اعمال شدن تغییرات یا کندی غیرعادی صفحات مواجه هستید، یکی از راهکارهای ساده این است که فقط Frontend را خارج از Docker اجرا کنید.

در سناریوی نمونه ما ابتدا سرویس‌های موردنیاز را اجرا کردیم:

Bash / Shell
docker compose --env-file .env.prod -f docker-compose.dev.yml up -d postgres redis backend
 
docker compose --env-file .env.prod -f docker-compose.dev.yml up -d celery_worker

سپس Next.js را روی سیستم Local اجرا کردیم:

Bash / Shell
cd frontend
 
export DJANGO_PUBLIC_API_URL=http://127.0.0.1:8000
export NEXT_PUBLIC_BASE_URL=http://localhost:3000
export NEXT_PUBLIC_NEXTJS_API_URL=http://localhost:3000
 
npm run dev

به این ترتیب سرویس‌هایی که اجرای آن‌ها در Docker برای ما مفید است همچنان Containerized باقی می‌مانند، ولی Next.js مستقیماً روی سیستم اجرا می‌شود.

این کار به‌خصوص زمانی مفید است که مشکل اصلی Development شما سربار File Sharing و Bind Mount باشد.

سوالات متداول

آیا برای این روش باید Docker را کنار بگذاریم؟

خیر. فقط Next.js را روی سیستم Local اجرا می‌کنیم و سرویس‌هایی مثل PostgreSQL، Redis و Backend همچنان داخل Docker باقی می‌مانند.

چرا backend:8000 دیگر کار نمی‌کند؟

چون backend معمولاً نام سرویس داخل شبکه Docker است. وقتی Next.js خارج از Docker اجرا می‌شود باید از پورتی که Backend روی Host منتشر کرده استفاده کنید؛ برای مثال:

Plain text
http://127.0.0.1:8000

آیا این روش برای Production هم مناسب است؟

این مقاله درباره بهبود تجربه Development است. معماری Production مسئله جداگانه‌ای است و می‌توانید همچنان Next.js را در Production داخل Docker اجرا کنید.

آیا خارج کردن Next.js از Docker همیشه سرعت را بیشتر می‌کند؟

نه لزوماً. ابتدا بهتر است مشخص کنید مشکل واقعاً از File Sharing، Bind Mount یا File Watching است. اگر اجرای Local تفاوت محسوسی ندارد، احتمالاً گلوگاه پروژه جای دیگری است.

اشتراک‌گذاری

لینک را کپی کنید یا مقاله را در تلگرام، واتساپ و لینکدین بفرستید.

نویسنده
م

محمدهادی حاجی حسینی

نویسنده مقالات آموزشی کدینگ‌یار

دوره‌های مرتبط

یادگیری عمیق‌تر با دوره‌های پروژه‌محور

مقالات VIP

در هر دوره‌ای شرکت کنید، به محتوای اختصاصی همان دوره دسترسی پیدا می‌کنید.