angularapachedeploymentcpanelserver-side-rendering

Publishing an SSR app w/ Angular 17 on a cPanel is possible, right?


When I build my Angular 17 SSR project with ng build, I get this:

  ├── dist
  │  └── my-project
  │    ├── .DS_Store
  │    ├── 3rdpartylicenses.txt
  │    ├── browser
  │    │  ├── .DS_Store
  │    │  ├── assets
  │    │  │  ├── ...
  │    │  ├── chunk-5XNHKK4I.js
  │    │  ├── ...
  │    │  ├── favicon.ico
  │    │  ├── index.html
  │    │  ├── main-OTXGCZUU.js
  │    │  ├── media
  │    │  │  └── ...
  │    │  ├── polyfills-RT5I6R6G.js
  │    │  ├── styles-6I36XN7I.css
  │    ├── prerendered-routes.json
  │    └── server
  │      ├── chunk-2VXXU7NI.mjs
  │      ├── ...
  │      ├── index.server.html
  │      ├── main.server.mjs
  │      ├── polyfills.server.mjs
  │      ├── render-utils.server.mjs
  │      └── server.mjs

Now my question is: How do I publish this to my cPanel / Traditional hosting provider?

I imagine that Angular made it easy for deploying SSR on Firebase, and other mainstream cloud services, but what about local cloud services using cPanel?

Dragging and dropping the /dist into the /public_html folder in the cPanel. Not the solution 😅


Solution

  • I found a solution!

    Small prerequisite: Check if your cPanel allows you to create NodeJS.

    In the root of the Angular project, I've created a main.js file, and here's what's inside of it:

    async function run() {
      try {
        // Import the app from the ES module
        const server = await import("./server/server.mjs");
        const app = await server.app();
    
        const port = process.env["PORT"] || 4000;
    
        // Start up the Node server
        app.listen(port, () => {
          console.log(`Node Express server listening on http://localhost:${port}`);
        });
      } catch (error) {
        console.error("Failed to import app:", error);
      }
    }
    
    run();
    

    And here's how my server.ts file looks like:

    import 'zone.js/node';
    
    import { APP_BASE_HREF } from '@angular/common';
    import { CommonEngine } from '@angular/ssr';
    import express from 'express';
    import { fileURLToPath } from 'url';
    import { dirname, join, resolve } from 'path';
    import bootstrap from './src/main.server';
    
    // The Express app is exported so that it can be used by serverless Functions.
    export function app(): express.Express {
      const server = express();
      const serverDistFolder = dirname(fileURLToPath(import.meta.url));
      const browserDistFolder = resolve(serverDistFolder, '../browser');
      const indexHtml = join(serverDistFolder, 'index.server.html');
    
      const commonEngine = new CommonEngine();
    
      server.set('view engine', 'html');
      server.set('views', browserDistFolder);
    
      // Example Express Rest API endpoints
      // server.get('/api/**', (req, res) => { });
      // Serve static files from /browser
      server.get(
        '*.*',
        express.static(browserDistFolder, {
          maxAge: '1y',
        })
      );
    
      // All regular routes use the Angular engine
      server.get('*', (req, res, next) => {
        const { protocol, originalUrl, baseUrl, headers } = req;
    
        commonEngine
          .render({
            bootstrap,
            documentFilePath: indexHtml,
            url: `${protocol}://${headers.host}${originalUrl}`,
            publicPath: browserDistFolder,
            providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
          })
          .then((html) => res.send(html))
          .catch((err) => next(err));
      });
    
      return server;
    }
    
    export * from './src/main.server';
    

    Then in my project I've ran ng build which generated me the aforementioned file tree.

    These are the steps I took:

    1. Open cPanel
    2. Click "Setup Node.js App"
    3. Click "Create Application"
    4. I gave it main.js as the "Application startup file", this is important.
    5. Click "Create"

    Here's how the instance looked at the end:

    enter image description here

    Then I navigated to the application's root folder in the server. This was the rough file structure:

    ├── main.js
    ├── public
    ├── ... (some other files)
    └── tmp
    

    Then I just dumped the /dist/my-project folder's contents inside of it, so the end result looks something like this:

    ├── .htaccess
    ├── 3rdpartylicenses.txt
    ├── browser
    ├── main.js
    ├── prerendered-routes.json
    ├── public
    ├── server
    └── tmp
    

    Then go back to the Node.js instance in the cPanel and hit "Restart"!

    Then the Node.js instance in cPanel just knows what to do, because of main.js. Hope this helps!