Most Angular deployment guides are written for one version, then quietly become outdated. You follow the steps, something breaks, and you have no idea why — because the guide was for Angular 17 and you're on Angular 19.
This guide is split by version. Find your path. Follow only that section.
Full deep-dive with all details at saurabhsharma.dev
Step 0 — Check Your Angular & Node.js Versions First
sh
ng version
node --version
Note the Angular version number. You'll need it to pick the right path below.
Step 1 — Node.js Compatibility (Read Before Building)
Your local Node.js must match what's available on your cPanel server. Mismatches silently fail npm install on the server.
| Angular Version | Minimum Node.js | Recommended |
|---|---|---|
| Angular 16 | 16.14.x | 18.x LTS |
| Angular 17 | 18.13.x | 18.x or 20.x LTS |
| Angular 18 | 18.19.x | 20.x LTS |
| Angular 19 | 20.11.x | 20.x or 22.x LTS |
Check what your cPanel supports:
cPanel → Setup Node.js App → + CREATE APPLICATION → Node.js version dropdown.
Mismatch? Use nvm to switch locally:
nvm install 20
nvm use 20
node --version # confirm
Choose Your Path
Does your project use SSR?
│
├─ NO → Path A — CSR (all versions)
│
└─ YES → Which version?
├─ Angular 16 → Path B
├─ Angular 17/18 → Path C
└─ Angular 19 → Path D
How to check if your project uses SSR:
grep -E '"@angular/ssr|@nguniversal' package.json
Line returned = SSR. Nothing = CSR.
Path A — CSR (Static) — All Angular Versions
No Node.js server needed. Just static files +
.htaccess.
1. Build
ng build
Important — the output folder changed in Angular 17:
# Angular 16 and below — upload from here:
dist/your-app-name/
# Angular 17 and above — upload from HERE (the browser subfolder):
dist/your-app-name/browser/
Uploading the wrong folder = blank page with no obvious error.
2. Upload to cPanel
Upload the contents of your build folder (not the folder itself) to public_html/.
Via File Manager: Zip the folder → upload → right-click → Extract.
Do NOT upload: node_modules/, .git/, .env, source .ts files.
3. Create .htaccess
This is the step most people miss. Without it, every route except / returns 404.
In cPanel File Manager → Settings → enable Show Hidden Files → create .htaccess:
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule . /index.html [L]
App in a subfolder (e.g. yourdomain.com/myapp/)? Change RewriteBase / to RewriteBase /myapp/.
4. Test
Visit your domain. Then try a deep route like /about directly in the URL bar. If it loads — you're done.
✅ CSR deployment complete.
Path B — SSR: Angular 16 (Universal)
Angular 16 uses
@nguniversaland needs aserver.tsmodification to work on Apache Passenger.
1. Modify server.ts
Replace this:
function run(): void {
const port = process.env['PORT'] || 4000;
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
With this:
function isRunningOnApachePassenger(): boolean {
return moduleFilename.includes('lsnode.js');
}
function run(): void {
const server = app();
if (isRunningOnApachePassenger()) {
server.listen(() => {
console.log('Node Express listening to Passenger Apache');
});
return;
}
const port = process.env['PORT'] || 4000;
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
Also update the conditional at the bottom of the file:
// Before
if (
moduleFilename === __filename ||
moduleFilename.includes('iisnode')
)
// After
if (
moduleFilename === __filename ||
moduleFilename.includes('iisnode') ||
isRunningOnApachePassenger()
)
2. Build
npm run build:ssr
Output:
dist/your-app-name/
├── browser/
└── server/
└── main.js ← this is your startup file
3. Upload
Create a directory outside public_html/ — e.g. /home/username/my-angular-app/.
⚠️ SSR apps cannot run from
public_html/on Namecheap shared hosting. This is a cPanel/Passenger constraint.
Zip your project (excluding node_modules/, .git/, README.md, .gitignore), upload there, extract.
4. cPanel Node.js App Setup
Setup Node.js App → + CREATE APPLICATION:
| Field | Value |
|---|---|
| Node.js version | Match your local (e.g. 18.x) |
| Application mode | Production |
| Application root | /home/username/my-angular-app/ |
| Application URL | Your domain |
| Application startup file | dist/your-app-name/server/main.js |
Then: Stop → Run NPM Install → Start App.
✅ Angular 16 SSR complete.
Path C — SSR: Angular 17 & 18
No
server.tschanges needed. Build command isng build. Startup file isserver.mjs.
1. Build
ng build
Output:
dist/your-app-name/
├── browser/
└── server/
└── server.mjs ← startup file (NOT main.js)
2. Upload
Same as Path B — directory outside public_html/. Zip without node_modules/, upload, extract.
3. cPanel Node.js App Setup
| Field | Value |
|---|---|
| Node.js version | 18.x or 20.x |
| Application mode | Production |
| Application root | /home/username/my-angular-app/ |
| Application URL | Your domain |
| Application startup file | dist/your-app-name/server/server.mjs |
Then: Stop → Run NPM Install → Start App.
✅ Angular 17/18 SSR complete.
Path D — SSR: Angular 19
SSR is built-in. ES Modules by default. Same
server.mjsstartup file.
1. Add SSR (if not already set up)
ng add @angular/ssr
2. Add ES Module support to package.json
{
"name": "my-angular-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "ng build",
"serve:ssr": "node dist/my-angular-app/server/server.mjs"
}
}
3. Build
ng build
Output:
dist/your-app-name/
├── browser/
│ ├── index.html
│ └── main.abc123.js
└── server/
└── server.mjs
4. Upload
Directory outside public_html/. Zip, upload, extract.
5. cPanel Node.js App Setup
| Field | Value |
|---|---|
| Node.js version | 20.x or 22.x |
| Application mode | Production |
| Application root | /home/username/my-angular-app/ |
| Application URL | Your domain |
| Application startup file | dist/your-app-name/server/server.mjs |
Stop → Run NPM Install → Start App.
✅ Angular 19 SSR complete.
Startup File Quick Reference
The single most common cPanel mistake — pointing to the wrong file:
| Angular Version | Startup File |
|---|---|
| Angular 16 (SSR) | dist/your-app-name/server/main.js |
| Angular 17 (SSR) | dist/your-app-name/server/server.mjs |
| Angular 18 (SSR) | dist/your-app-name/server/server.mjs |
| Angular 19 (SSR) | dist/your-app-name/server/server.mjs |
Performance: .htaccess With Caching + Compression
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule . /index.html [L]
# GZIP compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css
AddOutputFilterByType DEFLATE application/javascript application/json
</IfModule>
# Cache hashed JS/CSS forever
<FilesMatch "\.(js|css)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# Cache images 1 year
<FilesMatch "\.(jpg|jpeg|png|gif|ico|svg|webp|woff2)$">
Header set Cache-Control "public, max-age=31536000"
</FilesMatch>
# Always revalidate HTML
<FilesMatch "\.html$">
Header set Cache-Control "public, max-age=3600, must-revalidate"
</FilesMatch>
Troubleshooting
404 on all routes except /
→ .htaccess missing. Go to File Manager → Settings → enable Show Hidden Files → create it.
App looks unstyled (no CSS/images)
→ Wrong baseHref in angular.json. Set "baseHref": "/" and rebuild.
cPanel shows "Application is down"
→ Check Detected Configuration Files in the Node.js App panel for error output. Common causes:
- Wrong startup file path (check the table above)
- Wrong Node.js version (stop → change → NPM Install → start)
- Missing dependencies (stop → NPM Install → start)
npm install fails on server
→ Local Node.js doesn't match cPanel. Use nvm use 20 locally, rebuild, re-upload.
Routes break on page refresh (SSR)
→ Missing catch-all route in your server file:
app.get('*', (req, res) => {
res.sendFile(join(__dirname, '../browser/index.html'));
});
app.server.module not found (Angular 16)
→ @nguniversal not installed:
npm install @nguniversal/express-engine
npm run build:ssr
Version Comparison
| Feature | Angular 16 | Angular 17 | Angular 18 | Angular 19 |
|---|---|---|---|---|
| Min Node.js | 16.14 | 18.13 | 18.19 | 20.11 |
| SSR package | @nguniversal |
@angular/ssr |
@angular/ssr |
Built-in |
| Build command | npm run build:ssr |
ng build |
ng build |
ng build |
| Startup file | server/main.js |
server/server.mjs |
server/server.mjs |
server/server.mjs |
| server.ts change needed | ✅ Yes | ❌ No | ❌ No | ❌ No |
| ES Modules default | ❌ No | ❌ No | Optional | ✅ Yes |
Pre-Launch Checklist
- [ ] Local
node --versionmatches cPanel - [ ]
ng buildcompletes with zero errors - [ ] Uploaded correct folder (
browser/for Angular 17+, root dist for Angular 16 CSR) - [ ]
.htaccessin domain root (CSR deployments) - [ ]
node_modules/NOT uploaded — ran NPM Install in cPanel instead - [ ] Startup file path matches your version (see table above)
- [ ] All routes work including direct URL access
- [ ] Styles and images load
- [ ] HTTPS enabled (free Let's Encrypt in cPanel)
Found this useful? The full guide with every detail, decision tree, FAQ, and subfolder configs is at saurabhsharma.dev.
Drop a comment with your Angular version and deployment type — happy to help debug.
Top comments (0)