Mengatasi 403 Forbidden Saat Deploy Next.js ke Hostinger dengan output: “standalone”

Saya baru saja mengalami masalah yang cukup membingungkan ketika mencoba melakukan deployment aplikasi Next.js ke Hostinger Web App Hosting.

Proses build berhasil. Tidak ada vulnerability. Dashboard Hostinger menunjukkan deployment telah selesai dan aplikasi berstatus Running.

Namun, ketika domain dibuka, halaman yang tampil justru:

403 Forbidden
Access to this resource on the server is denied!

Hal yang membuat masalah ini semakin membingungkan adalah aplikasi berjalan normal di localhost. Halaman utama bisa dibuka, endpoint API bekerja, dan production build selesai tanpa error.

Setelah melakukan pemeriksaan satu per satu, ternyata masalahnya bukan pada domain, SSL, kode halaman, atau environment variables.

Masalahnya ada pada cara Next.js menghasilkan paket deployment.

Solusi yang akhirnya berhasil adalah menambahkan:

output: "standalone"

ke dalam konfigurasi Next.js.

Artikel ini menjelaskan proses diagnosisnya, alasan solusi tersebut bekerja, dan langkah-langkah yang bisa dicoba ketika mengalami masalah serupa.


Gejala yang Terjadi

Berikut kondisi deployment pada saat masalah muncul:

  • repository GitHub berhasil terhubung ke Hostinger,
  • framework terdeteksi sebagai Next.js,
  • proses npm install berhasil,
  • proses next build berhasil,
  • tidak ada vulnerability dari npm audit,
  • deployment berstatus Completed,
  • aplikasi berstatus Running,
  • domain dan SSL sudah aktif,
  • tetapi halaman utama tetap menampilkan 403 Forbidden.

Build log terlihat normal:

Creating an optimized production build ...
Compiled successfully
Generating static pages ...
Finalizing page optimization ...

Route (app)
┌ ○ /
├ ○ /_not-found
└ ƒ /api/health

Secara sekilas, tidak ada tanda bahwa deployment mengalami kegagalan.


Memastikan Aplikasi Berjalan di Localhost

Sebelum mengubah konfigurasi hosting, langkah pertama yang sebaiknya dilakukan adalah memastikan aplikasi memang dapat berjalan dalam mode production di komputer lokal.

Jalankan:

npm run build
npm run start

Kemudian buka:

http://127.0.0.1:3000

Pada kasus saya, halaman utama berhasil ditampilkan.

Endpoint API juga bekerja:

http://127.0.0.1:3000/api/health

Respons yang diterima:

{
  "status": "ok",
  "runtime": "nextjs",
  "timestamp": "2026-07-30T05:16:50.459Z"
}

Hasil ini membuktikan bahwa:

  • source code dapat dikompilasi,
  • Next.js dapat dijalankan dalam mode production,
  • halaman utama bekerja,
  • route dinamis bekerja,
  • masalah kemungkinan berada pada proses packaging atau runtime hosting.

Build Berhasil Belum Tentu Runtime Berhasil

Salah satu pelajaran penting dari masalah ini adalah:

Build yang berhasil tidak selalu berarti aplikasi server-side sudah memiliki runtime yang dapat menerima request.

Perintah berikut:

next build

memang menghasilkan folder .next.

Namun, folder .next pada konfigurasi default belum tentu menjadi paket deployment mandiri yang dapat langsung dijalankan oleh setiap platform hosting.

Pada banyak server, aplikasi Next.js default dijalankan menggunakan:

next start

Perintah tersebut masih mengandalkan struktur repository, package Next.js, dan dependency yang tersedia dalam environment server.

Hostinger menyediakan managed Web App Hosting yang menangani build, runtime Node.js, process management, routing, dan integrasi GitHub. Aplikasi Next.js yang menggunakan SSR, API route, atau fitur server-side memerlukan proses Node.js yang terus berjalan.


Memeriksa Struktur File Deployment

Langkah diagnosis berikutnya adalah membuka File Manager Hostinger.

Pada deployment yang bermasalah, struktur root hanya berisi:

.builds/
public_html/
DO_NOT_UPLOAD_HERE

Tidak ada folder:

nodejs/

Di dalam public_html, justru terdapat isi build .next, seperti:

public_html/
├── server/
├── static/
├── cache/
├── build-manifest.json
├── routes-manifest.json
└── file build Next.js lainnya

File .htaccess yang tersedia juga tidak meneruskan request ke runtime Node.js.

Isinya hanya berupa perlindungan terhadap folder build:

RewriteRule ^\.builds - [F,L]

Artinya, Apache menerima request dari browser, tetapi tidak memiliki aplikasi Node.js yang menjadi tujuan request tersebut.

Hasilnya adalah 403 Forbidden.


Membandingkan dengan Deployment Next.js yang Berhasil

Untuk mencari perbedaannya, saya membandingkan struktur tersebut dengan aplikasi Next.js lain yang berhasil berjalan di akun Hostinger yang sama.

Deployment yang berhasil memiliki struktur:

.builds/
nodejs/
public_html/

Di dalam folder nodejs, terdapat:

nodejs/
├── .next/
├── node_modules/
├── public/
├── tmp/
├── package.json
├── server.js
├── console.log
└── stderr.log

Sementara itu, public_html hanya berisi .htaccess.

Isi .htaccess mengarahkan request ke aplikasi Node.js melalui Passenger:

PassengerAppRoot /home/username/domains/example.com/nodejs
PassengerAppType node
PassengerNodejs /path/to/node
PassengerStartupFile server.js
PassengerBaseURI /
PassengerRestartDir /home/username/domains/example.com/nodejs/tmp

Alur request pada deployment yang benar menjadi:

Browser
   ↓
Apache
   ↓
.htaccess
   ↓
Passenger
   ↓
nodejs/server.js
   ↓
Next.js

Hostinger juga menjelaskan bahwa build aplikasi backend seperti Next.js ditempatkan di folder nodejs, sedangkan .htaccess di public_html digunakan untuk routing menuju runtime tersebut.


Akar Masalahnya: Tidak Ada Paket Standalone

Setelah membuka server.js dari deployment yang berhasil, ditemukan konfigurasi berikut:

"output": "standalone"

Di sinilah penyebab utama ditemukan.

Repository yang mengalami 403 belum menggunakan:

output: "standalone"

Akibatnya, Next.js hanya menghasilkan output build default dan Hostinger tidak memperoleh paket runtime mandiri yang berisi server.js.

Tanpa server.js, Hostinger tidak membentuk struktur runtime yang seharusnya digunakan Passenger untuk menjalankan aplikasi.


Solusi: Aktifkan Next.js Standalone Output

Buat atau edit file next.config.mjs di root repository:

/** @type {import("next").NextConfig} */
const nextConfig = {
  output: "standalone",
};

export default nextConfig;

Struktur sederhananya:

project/
├── app/
├── package.json
├── package-lock.json
└── next.config.mjs

Apabila menggunakan format CommonJS pada next.config.js, bentuknya dapat ditulis sebagai:

/** @type {import("next").NextConfig} */
const nextConfig = {
  output: "standalone",
};

module.exports = nextConfig;

Gunakan salah satu format yang sesuai dengan repository. Tidak perlu membuat keduanya.


Apa yang Dilakukan output: "standalone"?

Ketika konfigurasi standalone aktif, Next.js melakukan output file tracing.

Next.js menganalisis file, import, dependency, dan package yang benar-benar dibutuhkan aplikasi saat production. Kemudian Next.js menghasilkan paket runtime minimal di:

.next/standalone/

Strukturnya kurang lebih:

.next/
└── standalone/
    ├── server.js
    ├── package.json
    ├── node_modules/
    └── .next/

File server.js tersebut dibuat otomatis oleh Next.js. Kita tidak perlu menulis custom server sendiri.

Next.js menjelaskan bahwa standalone output membuat folder deployment berisi file yang diperlukan, dependency terpilih, serta server minimal yang dapat dijalankan tanpa menggunakan next start.


Menguji Standalone Output di Localhost

Setelah menambahkan konfigurasi, hapus hasil build lama dan jalankan build ulang:

rm -rf .next
npm run build

Periksa apakah file berikut sudah terbentuk:

test -f .next/standalone/server.js
test -f .next/standalone/package.json
test -d .next/standalone/node_modules

Jalankan standalone server:

PORT=3001 \
HOSTNAME=127.0.0.1 \
node .next/standalone/server.js

Kemudian buka:

http://127.0.0.1:3001

Pada kasus saya, halaman utama berhasil mengembalikan HTTP 200 dan endpoint API bekerja normal.

Catatan tentang file statis

Dokumentasi Next.js menyebutkan bahwa folder public dan .next/static tidak otomatis disalin ke folder standalone. Platform deployment dapat menangani proses tersebut sendiri. Untuk deployment manual, keduanya dapat disalin ke:

.next/standalone/public
.next/standalone/.next/static

Push ke GitHub dan Biarkan Hostinger Melakukan Deployment Ulang

Setelah standalone berhasil diuji secara lokal:

git add next.config.mjs
git commit -m "fix(deploy): enable Next.js standalone output"
git push origin main

Apabila auto-deployment Hostinger aktif, push ke branch yang terhubung akan memicu deployment baru secara otomatis.

Pastikan build log kembali berhasil:

Compiled successfully
Generating static pages
Finalizing page optimization

Setelah deployment selesai, buka domain.

Pada kasus saya, halaman yang sebelumnya menampilkan:

403 Forbidden

akhirnya berubah menjadi halaman Next.js yang benar.

Endpoint dinamis juga berhasil diakses:

https://example.com/api/health

Responsnya:

{
  "status": "ok",
  "runtime": "nextjs"
}

Struktur Deployment Setelah Berhasil

Setelah standalone output diterapkan, Hostinger membentuk struktur yang lebih tepat:

.builds/
nodejs/
public_html/

Folder nodejs berisi:

nodejs/
├── .next/
├── node_modules/
├── package.json
├── server.js
└── file runtime lainnya

Sementara itu, public_html/.htaccess meneruskan request ke nodejs/server.js melalui Passenger.

Dengan demikian, request tidak lagi berhenti di Apache dan dapat diproses oleh Next.js.


Mengapa Redeploy Saja Tidak Menyelesaikan Masalah?

Sebelum menemukan akar masalah, saya sempat melakukan redeploy tanpa mengubah kode.

Build kembali berhasil, tetapi website tetap 403.

Hal ini terjadi karena redeploy hanya membangun ulang konfigurasi repository yang sama.

Selama repository belum menghasilkan standalone runtime, hasil deployment tetap tidak memiliki server.js yang dibutuhkan.

Jadi, urutannya bukan hanya:

Redeploy

melainkan:

Tambahkan output: "standalone"
↓
Build dan uji lokal
↓
Commit
↓
Push
↓
Deploy ulang

Dokumentasi Hostinger menyatakan redeployment dapat membuat ulang .htaccess, tetapi apabila paket aplikasi yang dibutuhkan belum terbentuk, regenerasi routing saja belum tentu menyelesaikan penyebab dasarnya.


Checklist Troubleshooting 403 Next.js di Hostinger

Ketika mengalami masalah serupa, gunakan checklist berikut.

1. Pastikan aplikasi berjalan lokal

npm run build
npm run start

2. Pastikan route utama mengembalikan HTTP 200

curl -I http://127.0.0.1:3000

3. Periksa build log Hostinger

Pastikan tidak ada:

Build failed
Module not found
Environment variable missing
Out of memory

4. Periksa struktur file Hostinger

Deployment server-side yang benar seharusnya memiliki:

nodejs/
public_html/.htaccess

5. Periksa apakah server.js tersedia

nodejs/server.js

6. Tambahkan standalone output

const nextConfig = {
  output: "standalone",
};

7. Pastikan artefak lokal terbentuk

.next/standalone/server.js

8. Push dan deploy ulang

git push origin main

9. Uji halaman dan endpoint dinamis

https://example.com/
https://example.com/api/health

10. Jangan langsung mengedit .htaccess secara manual

Pada managed Web App Hosting, .htaccess dan konfigurasi Passenger sebaiknya dihasilkan oleh platform deployment.

Edit manual dapat hilang ketika deployment berikutnya dijalankan.


Apakah Semua Proyek Next.js Harus Menggunakan Standalone?

Tidak selalu.

Output default Next.js tetap valid dan dapat dijalankan menggunakan:

next build
next start

pada server atau platform yang memang dikonfigurasi untuk menjalankan struktur repository secara penuh.

Standalone lebih relevan ketika aplikasi perlu dikemas menjadi paket runtime yang:

  • lebih portabel,
  • memiliki server.js,
  • hanya membawa dependency yang dibutuhkan,
  • dijalankan oleh container, Passenger, atau platform deployment tertentu,
  • tidak bergantung pada seluruh repository production.

Jadi, output: "standalone" bukan pengganti semua metode deployment.

Namun, untuk kasus Hostinger Web App dengan gejala build berhasil tetapi domain 403 dan folder nodejs tidak terbentuk, konfigurasi ini layak menjadi pemeriksaan utama.


Standalone Bukan Static Export

Perlu dibedakan antara:

output: "standalone"

dan:

output: "export"

standalone tetap menjalankan server Node.js.

Fitur seperti berikut tetap dapat digunakan:

  • server-side rendering,
  • Route Handlers,
  • API endpoint,
  • Server Components,
  • autentikasi server-side,
  • koneksi database,
  • halaman dinamis.

Sementara output: "export" menghasilkan website statis tanpa runtime Node.js.

Apabila aplikasi menggunakan API route atau fitur backend Next.js, jangan mengganti standalone dengan static export hanya untuk menghindari masalah runtime.


Kesimpulan

Masalah 403 pada deployment Next.js di Hostinger dapat terlihat seperti masalah domain, permission, SSL, atau .htaccess.

Namun, pada kasus yang saya alami, akar masalahnya adalah aplikasi belum menghasilkan paket runtime standalone.

Build memang berhasil, tetapi Hostinger tidak memperoleh:

server.js

yang diperlukan untuk menjalankan Next.js melalui Passenger.

Solusinya adalah menambahkan:

const nextConfig = {
  output: "standalone",
};

Setelah build ulang, pengujian lokal, commit, dan deployment baru, Hostinger berhasil membentuk runtime Node.js dan website dapat diakses secara normal.

Ringkasnya:

Build sukses + website 403
↓
Periksa folder nodejs dan server.js
↓
Tambahkan output: "standalone"
↓
Build dan uji lokal
↓
Deploy ulang
↓
Website berhasil diakses

Semoga pengalaman ini membantu pengguna Hostinger lain yang mengalami kondisi serupa.

Salam Berkarya!
Putu Adi.

Keywords : Next.js output standalone, deploy Next.js ke Hostinger, Hostinger Web App, Next.js server.js, Next.js Passenger, error 403 Hostinger


belajar kombucha

Follow saya di sosial media

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted