You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: ARCHITECTURE.md
+15-18Lines changed: 15 additions & 18 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -206,7 +206,6 @@ app/src
206
206
└─ website.routes.ts
207
207
└─ app.component.ts
208
208
└─ app.routes.ts
209
-
└─ loader-shell.component.ts
210
209
└─ assets
211
210
└─ {images, icons (incl PWA icons), fonts, etc.}
212
211
└─ environments
@@ -253,13 +252,13 @@ As things grow you may need to adapt and tweak this structure (e.g. to add anoth
253
252
254
253
|**:brain: Design decision**|
255
254
| :-- |
256
-
| 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). |
255
+
| 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). |
257
256
258
257
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).
259
258
260
-
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).
259
+
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).
261
260
262
-
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).
261
+
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).
263
262
264
263
> [!NOTE]
265
264
>
@@ -273,13 +272,10 @@ For the build-time prerendering of pages, we:
273
272
274
273
- Configure the `prerender` option in `angular.json` to prerender all paths defined in the [`app/prerendered-routes.txt`](./app/prerendered-routes.txt) file.
275
274
- We also set `"discoverRoutes": false` so only the routes we explicitly specify are prerendered.
276
-
- Specify all static paths we want prerendered, in the `prerendered-routes.txt` file.
275
+
- Specify all static paths we want prerendered, in the aforementioned `prerendered-routes.txt` file.
277
276
- Out of the box, we have the website home page (`/`) and the about page (`/about`).
278
-
- Specify the `/loader` path in the `prerendered-routes.txt` file, so that the loader shell is prerendered too.
279
-
- 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.
280
-
- This is used as the default HTML file to serve for all fully dynamic parts of the app.
281
277
282
-
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.
278
+
So, when we run the production build (`pnpm build`) Angular will output separate static HTML files for the prerendered routes.
283
279
284
280
> [!NOTE]
285
281
>
@@ -293,7 +289,7 @@ Given we have a mix of prerendered static and fully dynamic pages, we have to co
293
289
>
294
290
> 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).
295
291
>
296
-
> 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.
292
+
> 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.
297
293
298
294
For the static pages (prerendered), we:
299
295
@@ -305,10 +301,11 @@ For the static pages (prerendered), we:
305
301
306
302
Then, for the rest of the fully dynamic pages, we:
307
303
308
-
- 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.
304
+
- 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.
309
305
- This is known as a "catch-all" rule, and MUST be the last item in the list of rewrite rules.
310
-
- 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.
311
-
- 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.
306
+
- 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.
307
+
- I.e. this will be used by the service worker for all dynamic pages.
308
+
- 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.
312
309
313
310
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.
314
311
@@ -414,7 +411,7 @@ To try out the login flow run the app locally and click on the "Login" button.
414
411
>
415
412
> 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.
416
413
>
417
-
> 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).
414
+
> 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.
418
415
419
416
## [`app`] Logging
420
417
@@ -454,9 +451,9 @@ Most of the components, services, etc. provided in the base template have corres
454
451
455
452
|**:brain: Design decision**|
456
453
| :-- |
457
-
| We use [ESLint](https://eslint.org/) for linting and [Prettier](https://prettier.io/) for code formatting.<br><br>Running `pnpm lint` performs the linting, and all Prettier formatting is carried out within VS Code (e.g. when you save a file). |
454
+
| We use [ESLint](https://eslint.org/) for linting and [Prettier](https://prettier.io/) for code formatting.<br><br>Running `pnpm lint` performs the linting only, and all Prettier formatting is carried out within VS Code (e.g. when you save a file). |
458
455
459
-
The config for linting is in [`app/.eslintrc.json`](./app/.eslintrc.json) and for formatting in [`app/.prettierrc`](./app/.prettierrc).
456
+
The config for linting is in [`app/eslint.config.js`](./app/eslint.config.js) and for formatting in [`app/.prettierrc`](./app/.prettierrc).
460
457
461
458
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.
462
459
@@ -551,9 +548,9 @@ See the files within the [`firebase/test`](./firebase/test/) folder for the secu
551
548
552
549
|**:brain: Design decision**|
553
550
| :-- |
554
-
| We use [ESLint](https://eslint.org/) for linting and [Prettier](https://prettier.io/) for code formatting.<br><br>Running `pnpm lint` performs the linting, and all Prettier formatting is carried out within VS Code (e.g. when you save a file). |
551
+
| We use [ESLint](https://eslint.org/) for linting and [Prettier](https://prettier.io/) for code formatting.<br><br>Running `pnpm lint` performs the linting only, and all Prettier formatting is carried out within VS Code (e.g. when you save a file). |
555
552
556
-
The config for linting is in [`firebase/.eslintrc.js`](./firebase/.eslintrc.js) and for formatting in [`firebase/.prettierrc`](./firebase/.prettierrc).
553
+
The config for linting is in [`firebase/eslint.config.js`](./firebase/eslint.config.js) and for formatting in [`firebase/.prettierrc`](./firebase/.prettierrc).
557
554
558
555
## Continuous integration (CI) using GitHub Actions
0 commit comments