Search code examples
node.jsangularheroku

Problem deploying Angular + Node with Heroku


I struggle deploying my node.js(TS)/angular app with heroku.

When I try to deploy it on git with git push heroku master, every step go fine until the heroku-postbuild one.

Hence, it returns the following :

Generating browser application bundles (phase: setup)...
remote: An unhandled exception occurred: ENOENT: no such file or directory, lstat '/tmp/build_24879b07/front/node_modules'
remote: See "/tmp/ng-REYivA/angular-errors.log" for further details.

My project architecture is...

Project
|
|-- api/...
|-- front/...
|-- package.json
|-- ...

...which might cause some problems.

In addition, here are the most probably concerned files :

./package.json

{
  "name": "mymusicads",
  "scripts": {
    "heroku-postbuild": "cd front && ng build --configuration production",
    "start": "cd api && npm run start"
  },
  "engines": {
    "node": "16.x",
    "npm": "7.x"
  },
  "dependencies": {
    "@angular-devkit/build-angular": "^12.2.12",
    "@angular/cli": "^11.2.13",
    "@angular/common": "^11.2.13",
    "@angular/compiler": "^12.2.12",
    "@angular/compiler-cli": "^11.2.13",
    "@angular/core": "^11.2.13",
    "@angular/platform-browser": "^11.2.13",
    "rxjs": "^6.6.7",
    "typescript": "^4.3.5"
  }
}

./api/index.ts

import express, { Request, Response } from 'express';
import * as dotenv from 'dotenv';
import path from 'path';
import { adDataRoutes } from './app/routes/ad-data.route';
import { errorHandler } from './middleware/error.middleware';
import { notFoundHandler } from './middleware/not-found.middleware';

dotenv.config();

const app = express();

app.use(express.json());
app.use('/api/addata', adDataRoutes);
app.use(express.static(path.join(__dirname, 'mymusicads', 'dist', 'front')));

app.use(errorHandler);
app.use(notFoundHandler);

app.get('/', (req: Request, res: Response) => {
  res.sendFile(path.join(__dirname, 'mymusicads', 'dist', 'front', 'index.html'));
});

app.listen(process.env.PORT || 3000, () => {
  console.log(`Listening on port ${process.env.PORT || 3000}`);
});

./front/angular.json

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "front": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        },
        "@schematics/angular:application": {
          "strict": true
        }
      },
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/front",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "aot": true,
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
              "src/styles.scss"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "3mb",
                  "maximumError": "6mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "10kb",
                  "maximumError": "20kb"
                }
              ]
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "front:build",
            "proxyConfig": "proxy.conf.json"
          },
          "configurations": {
            "production": {
              "browserTarget": "front:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "front:build"
          }
        },
        "test": { [...] },
        "lint": { [...] }
        }
      }
    }
  },
  "defaultProject": "front"
}

From what I understood, the problem come from my api/index.ts file, I may not be targeting the right path with res.sendFile(path.join(__dirname, 'mymusicads', 'dist', 'front', 'index.html')).

Thanks !


Solution

  • Can't recall if I ran into the same issue exactly, but had a different setup. In front folder I had a package.json specifically for Angular app - basically the default Angular CLI setup

    Top level package.json had very few dependencies (if any), mainly scripts (client folder where you use front). For prebuild, run npm install in client folder, and npm run --prefix client build -- -- prod

    For the server:

    // Static files
    app.use(
        express.static(path.join(__dirname, '../client/dist/my-app'), {
            maxAge: '1y',
        })
    );
    // Angular app
    app.get('*', (req, res) => {
        res.sendFile(
            path.join(__dirname, '../client/dist/my-app/index.html')
        );
    });