diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bcea35e..8e784ef 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -30,9 +30,9 @@ jobs:
${{ runner.os }}-firebase-emulators-
- name: Set up pnpm
- uses: pnpm/action-setup@v3
+ uses: pnpm/action-setup@v4
with:
- version: 8
+ version: 9
- name: Set up Node.js
uses: actions/setup-node@v4
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index dcff49c..24b1789 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -206,13 +206,10 @@ app/src
└─ website.routes.ts
└─ app.component.ts
└─ app.routes.ts
- └─ loader-shell.component.ts
└─ assets
└─ {images, icons (incl PWA icons), fonts, etc.}
└─ environments
└─ {environment files - live, test and local}
-└─ styles
- └─ {SCSS partials and utilities}
└─ test
└─ helpers
└─ {test helpers}
@@ -253,13 +250,13 @@ As things grow you may need to adapt and tweak this structure (e.g. to add anoth
| **:brain: Design decision** |
| :-- |
-| Out of the box, we _don't_ use server-side rendering (SSR). We _do_ use prerendering for certain pages (configured explicitly), and everything else is fully dynamic (i.e. client-side only, with a minimal loader shell). |
+| Out of the box, we _don't_ use server-side rendering (SSR). We _do_ use prerendering for certain pages (configured explicitly), and everything else is fully dynamic (i.e. client-side only). |
Whilst Angular has very good [support for server-side rendering (SSR)](https://angular.dev/guide/ssr) we don't make use of this in the _deployed app_ as we want to be able to run the app wholly from static assets (i.e. no dynamic server required to render any pages).
-Instead, we do make use of [build-time prerendering](https://angular.dev/guide/prerendering) for routes we explicitly specify in [`app/prerendered-routes.txt`](./app/prerendered-routes.txt) (currently the website home and about pages) — a static HTML file is built and served for each path specified there (with some additional Firebase Hosting and PWA configuration).
+Instead, we do make use of [build-time prerendering](https://angular.dev/guide/prerendering) for routes we explicitly specify in [`app/prerendered-routes.txt`](./app/prerendered-routes.txt) file (currently the website home and about pages) — a static HTML file is built and served for _each_ path specified there (with some additional Firebase Hosting and PWA configuration required to support these).
-And then everything else in the app is fully dynamic (i.e. rendered on the client) — a special empty "loader" HTML file (prerendered from the [`LoaderShellComponent`](./app/src/app/loader-shell.component.ts)) is served for all these routes, and we've configured Firebase Hosting and the PWA set-up to serve this loader file for these routes (more details below).
+And then everything else in the app is fully dynamic (i.e. rendered on the client) — the special `index.csr.html` generated by the Angular build is used in the Firebase Hosting config and the PWA set-up as the file to serve for all non-prerendered routes (more details below).
> [!NOTE]
>
@@ -273,13 +270,10 @@ For the build-time prerendering of pages, we:
- Configure the `prerender` option in `angular.json` to prerender all paths defined in the [`app/prerendered-routes.txt`](./app/prerendered-routes.txt) file.
- We also set `"discoverRoutes": false` so only the routes we explicitly specify are prerendered.
-- Specify all static paths we want prerendered, in the `prerendered-routes.txt` file.
+- Specify all static paths we want prerendered, in the aforementioned `prerendered-routes.txt` file.
- Out of the box, we have the website home page (`/`) and the about page (`/about`).
-- Specify the `/loader` path in the `prerendered-routes.txt` file, so that the loader shell is prerendered too.
- - The `/loader` route serves an empty shell of the app (using the [`LoaderShellComponent`](./app/src/app/loader-shell.component.ts)), which then loads the full app on the client-side. This route is defined in the [`app.routes.ts`](./app/src/app/app.routes.ts) file.
- - This is used as the default HTML file to serve for all fully dynamic parts of the app.
-So, when we run the production build (`pnpm build`) Angular will output static HTML files for the prerendered routes (including an HTML file for the loader shell) together with the usual JavaScript, CSS, etc. assets.
+So, when we run the production build (`pnpm build`) Angular will output separate static HTML files for the prerendered routes.
> [!NOTE]
>
@@ -293,7 +287,7 @@ Given we have a mix of prerendered static and fully dynamic pages, we have to co
>
> In a typical single-page app (SPA) without any server side rendering or static page generation, you'd serve a static `index.html` file for all paths requested. This file would usually contain very little UI, and then bootstrap the app and handle routing, data fetching, templating, etc. on the client-side (all handled by your framework).
>
-> However, in our case, the `index.html` file is now the static (prerendered) website home page (which still bootstraps the Angular app when it loads client-side), which we wouldn't want to serve for all routes in our app as it contains content for the home page. And we have a mix of static pages and fully dynamic pages that need to work regardless of whether they are requested directly (from the "server" — a static host, Firebase Hosting, in our case) or within the single-page app (client-side). Hence the need for the loader shell and the Firebase Hosting and PWA set-up.
+> However, in our case, the `index.html` file is now the static (prerendered) website home page (which still bootstraps the Angular app when it loads client-side), which we wouldn't want to serve for all routes in our app as it contains content for the home page. And we have a mix of static pages and fully dynamic pages that need to work regardless of whether they are requested directly (from Firebase Hosting) or within the single-page app (client-side). As part of the build, Angular outputs a special `index.csr.html` file which we make use of for all routes not covered by the prerendered pages.
For the static pages (prerendered), we:
@@ -305,10 +299,11 @@ For the static pages (prerendered), we:
Then, for the rest of the fully dynamic pages, we:
-- Add an entry in the [`firebase/firebase.json`](./firebase/firebase.json) file (under the `hosting.rewrites` key) to serve the `/loader` path (the default loader shell, mentioned previously) for all paths that aren't explicitly covered by the static pages.
+- Add an entry in the [`firebase/firebase.json`](./firebase/firebase.json) file (under the `hosting.rewrites` key) to serve the special `/index.csr.html` file for all paths that aren't explicitly covered by the static (aka prerendered) pages.
- This is known as a "catch-all" rule, and MUST be the last item in the list of rewrite rules.
-- Configure the PWA service worker (in the [`app/ngsw-config.json`](./app/ngsw-config.json) file) to use the `"/loader/index.html"` path as the default "index" file to serve for all paths not covered by those defined in the `navigationUrls` key.
- - In this same file, we also add `"/loader/index.html"` to the list of prefetched URLs so it can be cached by the service worker.
+- Configure the PWA service worker (in the [`app/ngsw-config.json`](./app/ngsw-config.json) file) to use the same `"/index.csr.html"` path as the default "index" file to serve for all paths not covered by those defined in the `navigationUrls` key.
+ - I.e. this will be used by the service worker for all dynamic pages.
+ - In this same file, we also add `"/index.csr.html"` to the list of prefetched URLs so it can be cached by the service worker.
Note also: in the [`firebase/firebase.json`](./firebase/firebase.json) file (under the `hosting` key) we set `"cleanUrls": true` and `"trailingSlash": false` to normalize the behavior and ensure our static paths are served correctly.
@@ -338,9 +333,9 @@ The [`app/src/app/app.component.ts`](./app/src/app/app.component.ts) file contai
| **:brain: Design decision** |
| :-- |
-| We use [Angular Material](https://material.angular.io/) for UI components, and [Tailwind CSS](https://tailwindcss.com/) for styling. You can still create your own UI components or add in other libraries, if needed. You can also customize Tailwind CSS as you wish. |
+| We use [Angular Material](https://material.angular.io/) (with Material 3) for UI components, and [Tailwind CSS](https://tailwindcss.com/) for styling. You can still create your own UI components or add in other libraries, if needed. You can also customize Tailwind CSS as you wish, by updating the [`app/tailwind.config.js`](./app/tailwind.config.js) config file. |
-The [`app/src/styles.scss`](./app/src/styles.scss) file sets up both Angular Material (with a basic theme) and Tailwind CSS styling. Here, we also provide styling overrides to make Angular Material work okay with the Tailwind CSS base styles.
+The [`app/src/styles.scss`](./app/src/styles.scss) file sets up both Angular Material (with a basic Material 3 theme with custom background and text colors) and Tailwind CSS styling. Here, we also provide styling overrides to make Angular Material work okay with the Tailwind CSS base styles.
> [!NOTE]
>
@@ -414,7 +409,7 @@ To try out the login flow run the app locally and click on the "Login" button.
>
> Firebase Authentication does not provide server-side sessions, which is not a problem for us as we don't use server-side rendering (SSR), and for any server-side functionality we use Firebase Functions (which has access to the auth token in each request). All authentication is carried out and managed client-side using the Firebase JavaScript SDK.
>
-> This does mean that in the auth guard we need a check to see if we're running server-side (currently only applicable to local development and running the build process), where we then "redirect" to the special `/loader` route (covered in a previous section). Note that this isn't a proper redirect, just something that happens purely server-side to determine what content gets rendered for that route (so it doesn’t actually change the path the user is requesting). Once the page loads in the browser then the usual client-side auth check takes over when the Angular app hydrates (i.e. fully loads up).
+> This does mean that in the auth guard we need a check to see if we're running server-side and then short-circuit the logic and return `false`. Note that, currently, this is only applicable to local development, since we don't use SSR in production. Once the page loads in the browser then the usual client-side auth check takes over when the Angular app hydrates (i.e. fully loads up). You may see the error `ERROR RuntimeError: NG04002: Cannot match any routes.` in the dev process output — you can safely ignore this as it will only happen in local development.
## [`app`] Logging
@@ -454,9 +449,9 @@ Most of the components, services, etc. provided in the base template have corres
| **:brain: Design decision** |
| :-- |
-| We use [ESLint](https://eslint.org/) for linting and [Prettier](https://prettier.io/) for code formatting.
Running `pnpm lint` performs the linting, and all Prettier formatting is carried out within VS Code (e.g. when you save a file). |
+| We use [ESLint](https://eslint.org/) for linting and [Prettier](https://prettier.io/) for code formatting.
Running `pnpm lint` performs the linting only, and all Prettier formatting is carried out within VS Code (e.g. when you save a file). |
-The config for linting is in [`app/.eslintrc.json`](./app/.eslintrc.json) and for formatting in [`app/.prettierrc`](./app/.prettierrc).
+The config for linting is in [`app/eslint.config.js`](./app/eslint.config.js) and for formatting in [`app/.prettierrc`](./app/.prettierrc).
We also integrate [`prettier-plugin-tailwindcss`](https://www.npmjs.com/package/prettier-plugin-tailwindcss) to format Tailwind CSS classes in your HTML and JavaScript files.
@@ -551,9 +546,9 @@ See the files within the [`firebase/test`](./firebase/test/) folder for the secu
| **:brain: Design decision** |
| :-- |
-| We use [ESLint](https://eslint.org/) for linting and [Prettier](https://prettier.io/) for code formatting.
Running `pnpm lint` performs the linting, and all Prettier formatting is carried out within VS Code (e.g. when you save a file). |
+| We use [ESLint](https://eslint.org/) for linting and [Prettier](https://prettier.io/) for code formatting.