Search code examples
angulargithub-pages

Error on building json files on Angular 19 app and GitHub Pages - 404 on Routing


I'm trying to deploy an Angular app to GitHub Pages. I'm using Angular 19 and Node 22. I've been days on it but due to different versions and tutorials I'm quite confused on how to deploy my app and keep getting an error where only the main page shows up but the Json files from the services folder do not show and I get a 404 on the console. When I refresh the page, it then shows me a 404 on the ui as the routing seems to break. The way I'm trying to deploy the app is by building the docs folder with npm run build command, removing the browser folder and pushing to the GitHub master branch.

This is my configuration on GitHub Pages: enter image description here

And here my related files:

// package.json
{
  "name": "bible-quiz",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --configuration=production --output-path docs --base-href /bible-quiz/ && mv docs/browser/* docs/ && rmdir docs/browser",
    "watch": "ng build --watch --configuration development",
    "test": "ng test"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^19.1.0",
    "@angular/common": "^19.1.0",
    "@angular/compiler": "^19.1.0",
    "@angular/core": "^19.1.0",
    "@angular/forms": "^19.1.0",
    "@angular/platform-browser": "^19.1.0",
    "@angular/platform-browser-dynamic": "^19.1.0",
    "@angular/router": "^19.1.0",
    "@tailwindcss/postcss": "^4.0.0",
    "postcss": "^8.5.1",
    "rxjs": "~7.8.0",
    "tailwindcss": "^4.0.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.15.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^19.1.4",
    "@angular/cli": "^19.1.4",
    "@angular/compiler-cli": "^19.1.0",
    "@types/jasmine": "~5.1.0",
    "jasmine-core": "~5.5.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "typescript": "~5.7.2"
  }
}
// angular.json
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "bible-quiz": {
      "projectType": "application",
      "schematics": {},
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:application",
          "options": {
            "outputPath": "docs",
            "index": "src/index.html",
            "browser": "src/main.ts",
            "polyfills": [
              "zone.js"
            ],
            "tsConfig": "tsconfig.app.json",
            "assets": [
              {
                "glob": "**/*",
                "input": "public",
                "output": "/"
              }
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kB",
                  "maximumError": "1MB"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "4kB",
                  "maximumError": "8kB"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "configurations": {
            "production": {
              "buildTarget": "bible-quiz:build:production"
            },
            "development": {
              "buildTarget": "bible-quiz:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n"
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "polyfills": [
              "zone.js",
              "zone.js/testing"
            ],
            "tsConfig": "tsconfig.spec.json",
            "assets": [
              {
                "glob": "**/*",
                "input": "public"
              }
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          }
        }
      }
    }
  }
}
// app.routes.ts
import {Routes} from '@angular/router';
import {SubjectListComponent} from './components/subject-list/subject-list.component';
import {SubjectComponent} from './components/subject/subject.component';
import {InnerSubjectListComponent} from './components/inner-subject-list/inner-subject-list.component';

export const routes: Routes = [
  {path: '', pathMatch: 'full', redirectTo: 'subjects'},
  {path: 'subjects', component: SubjectListComponent},
  {path: 'subjects/:parent-id/inner-subjects', component: InnerSubjectListComponent},
  {path: 'subjects/:parent-id/inner-subjects/:inner-subject-id', component: SubjectComponent},
];
<!-- index.html -->
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>BibleQuiz</title>
  <base href="/bible-quiz/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>
// subjects.service.ts
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';
import {ISubject} from '../models/ISubject';
import {environment} from '../../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class SubjectsService {
  private subjectsUrl = environment.apiUrl + 'subjects.json';

  constructor(private http: HttpClient) {
  }

  getSubjects(): Observable<ISubject[]> {
    return this.http.get<ISubject[]>(this.subjectsUrl);
  }
}
// environment.prod.ts
export const environment = {
  production: true,
  apiUrl: '/bible-quiz/'
};
// environment.ts
export const environment = {
  production: false,
  apiUrl: '/'
};

The environments have been added as it's my understanding they are needed due to GitHub Pages appending an extra /bible-quiz/ subroot to the page, but not sure this is really needed.

This is my full app: https://github.com/francislainy/bible-quiz

And here the url I'm trying to load: https://francislainy.github.io/bible-quiz

And an image for what I see in my console:

enter image description here


Solution

  • After extensive discussion with @dapperdandev, it was noticed the issue seemed to do not with the deployment but with the routing for Angular apps and Github pages.

    As per this blog post https://benfraserdesign.medium.com/deploying-an-angular-app-on-github-pages-c4dfee672968, copying the index.html file and naming it 404.html into the docs folder seems to work as a workaround for the issue.

    So I updated my build command from:

    "build": "ng build --configuration=production --output-path docs --base-href /bible-quiz/ && mv docs/browser/* docs/ && rmdir docs/browser",
    

    to:

    "build": "ng build --configuration=production --output-path docs --base-href /bible-quiz/ && mv docs/browser/* docs/ && rmdir docs/browser && cp docs/index.html docs/404.html",
    

    And that fixed the issue.

    PS: Other than this, I tried to use hash routing as per:

    https://stackoverflow.com/a/75993642/6654475

    But that didn't seem to fix it for my case, but maybe would be a better path for a solution if having a 404.html file as a duplicate of the index.html file may not suit your case.