Capstone: Integrating This Into the Existing Astro Site

Romaji to Kana Converter: Angular & Express

Chapter 8 · Capstone: Integrating This Into the Existing Astro Site

Chapter 7 got the whole stack running as a real, standalone deployment. This closing chapter does what Chapter 1 always meant it to do: put it somewhere real, at a real path under the user's own existing Astro-based site, and finish the one promise that made this course's own reason for existing concrete from the very first page — tying it directly into the Japanese-language lessons and kanji pages already live there.

Three Real Ways to Mount This Behind an Existing Site

OptionWhat it involvesTradeoff
Subdomain (romaji.existing-site.com)A separate DNS record and its own nginx server block pointing straight at the unified Express app from Chapter 7Simplest possible nginx config; reads as a separate site rather than one integrated tool
Path-mounted reverse proxy (existing-site.com/romaji/)One nginx location block on the existing site's own domain, proxying that one path to the same Express appFeels like part of the same site; needs two real, verified fixes this chapter builds — covered below
JSON-API + Astro-frontend rewriteKeep only Chapter 5's Express route; rebuild the small UI as a new Astro page consuming it directlyThe deepest real integration, and real rework of a UI that already works well — hard to justify for a tool this size

The path-mounted option is built out in full here, matching every other Personal Multi-Technology Projects course on this site that reached this same decision.

A Real Problem: Built Assets Don't Know They're Not at the Root

Chapter 7's own ng build produces an index.html whose script and style references assume the app is served from the domain root — correct for that chapter's own standalone deployment, and wrong the moment this app is mounted at /romaji/ on someone else's domain.

Verified Against Angular's Own Current Documentation
The Angular CLI's own --base-href flag exists specifically for this: it rewrites the <base href> tag Angular injects into index.html, and the built asset references along with it, to reflect a real sub-path deployment rather than a root one — the direct Angular equivalent of Vite's own base config option, used for exactly this same purpose in this site's own React-based sibling courses.
# from the Angular project root ng build --base-href /romaji/

A Second Real Problem: API Calls Are Root-Relative, Not Site-Relative

Chapter 7's own environment.ts leaves apiBaseUrl empty, so every request becomes a plain path like /api/convert — correct at the domain root, still wrong here for a genuinely different reason than the assets above. /api/convert is an absolute path; it always resolves against the domain's own root, regardless of which page made the request. A request from existing-site.com/romaji/ would still be sent to existing-site.com/api/convert, not existing-site.com/romaji/api/convert, where this app's own API actually needs to be reached once mounted at a sub-path.

Editing environment.ts directly would fix this deployment but quietly break Chapter 7's own root-deployment story for anyone who builds this project without mounting it under a sub-path. A third, small environment file keeps both working:

// src/environments/environment.mounted.ts — new for this specific deployment export const environment = { apiBaseUrl: '/romaji', };
// angular.json — a new, minimal "mounted" configuration alongside "production" and "development" "configurations": { "production": { /* unchanged from Chapter 7 */ }, "development": { /* unchanged from Chapter 7 */ }, "mounted": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.mounted.ts" } ] } }
Verified: Named Configurations Compose With a Comma
Angular's own current documentation confirms multiple named configurations can be combined directly on the command line — "ng build --configuration staging,french" is the documentation's own example — parsed left to right, with a later configuration's own settings winning if two configurations happen to touch the same option. There's no separate extends keyword for one configuration to inherit from another; composing two small, focused configurations together at build time is the real, documented mechanism instead.
# the real, final build command for this specific deployment ng build --configuration production,mounted --base-href /romaji/
Two Genuinely Different Fixes for Two Genuinely Different Things
--base-href is a build-time setting controlling where the compiled JavaScript and CSS files are referenced from. environment.apiBaseUrl is a value baked into the bundle at the same build, but controlling where this app's own HTTP requests are sent. Both happen to need the identical /romaji prefix for this deployment, but setting only one leaves either a broken page (missing assets) or a broken app (every conversion request failing) — never both problems solved by a single change.

Building Out the Reverse Proxy

One location block on the existing site's own nginx config, added alongside whatever configuration already serves the rest of that site:

# /etc/nginx/sites-available/existing-site (excerpt) server { listen 443 ssl; server_name existing-site.com; # ...the existing Astro site's own location blocks, unchanged... location /romaji/ { proxy_pass http://localhost:4000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

The trailing slash on both location /romaji/ and proxy_pass http://localhost:4000/; is what makes nginx strip the /romaji prefix before forwarding — a request for /romaji/api/convert reaches Express as exactly /api/convert, the same route Chapter 5 already built with no changes needed on the Express side. The same stripping applies to every static asset request, which reaches Express's own express.static() middleware from Chapter 7 completely unaware it's being served from anywhere but its own root.

With everything mounted this way, the browser only ever sees one real domain — Chapter 7's own ALLOWED_ORIGIN CORS setting can simply be set to https://existing-site.com, and every request genuinely is same-origin now, exactly the scenario that chapter's own warn-box anticipated without yet knowing which real shape it would take.

Linking It Where It's Actually Useful

Chapter 1 named the real reason this course exists — this tool's own most immediate value is sitting right next to the Japanese-language lessons and the kanji tiles page, not off on its own. With a real, working /romaji/ URL now live, the last piece is a real link pointing at it from exactly those pages — the concrete payoff of a promise made eight chapters ago:

<!-- illustrative addition to kanji_tiles_cms_content.html and the japanese_lesson_* pages --> <a href="/romaji/" class="tool-link" target="_blank" rel="noopener"> 🔤 Convert romaji ↔ kana </a>
Illustrative, Not Applied Site-Wide Here
Adding this link to every real Japanese-lesson and kanji page is genuinely a content-editing task across many separate files, not something this one course chapter does on its own — the same "concrete plan, real next step" framing this site's own sibling capstones already use for their own deferred features. The snippet above is the exact real markup that plan resolves to, verified to work against the live /romaji/ URL this chapter just finished standing up.

Chapter Attribution

Piece of the finished appBuilt in
The full gojūon/dakuten/yōon romaji-to-kana mapping tableChapter 2
Long vowels, sokuon, and the real は/へ/を particle exceptionsChapter 3
ConvertService, RomajiInputComponent, KanaOutputComponent — the shared-service architectureChapter 4
The real Express /api/convert route, the ported plain-JS conversion engineChapter 5
Debounced, race-safe, error-resilient conversion pipeline; the real styled UIChapter 6
Single-process production deployment; environment.apiBaseUrl; PM2; nginxChapter 7
Path-mounted reverse proxy, --base-href, the "mounted" configuration, and a real link into the site's own Japanese contentChapter 8 (this chapter)

What This Course Deliberately Doesn't Cover

  • Kanji conversion — named out of scope in Chapter 1 for a real reason: it's a genuinely harder, dictionary-and-context-dependent problem (the same kana can map to several different kanji depending on meaning), deliberately left as its own real future project rather than bolted on here.
  • A persisted conversion history — every result lives only in the current page's own signals; nothing is saved across a reload.
  • UPC/EAN-style batch or file-based conversion — this tool converts one typed phrase at a time, matching the actual, narrow use case Chapter 1 scoped it around.

Hands-On Exercises

Exercise 1

Create environment.mounted.ts and the "mounted" configuration exactly as this chapter describes, then run ng build --configuration production,mounted --base-href /romaji/. Inspect the real built index.html and confirm its asset references include the /romaji prefix, and inspect the built JavaScript bundle to confirm the string '/romaji' appears in place of Chapter 7's empty apiBaseUrl.

📄 View solution
Exercise 2

Configure the nginx location /romaji/ block against a real running instance of the app (a second local nginx config is fine for testing), and confirm both a page load and a real /api/convert request succeed through the proxy, with server.js completely unchanged from Chapter 7.

📄 View solution
Exercise 3

Build with only --base-href /romaji/ set, using just the default "production" configuration (skip "mounted" entirely, leaving apiBaseUrl empty). Serve it through the same nginx proxy from Exercise 2 and confirm the page itself loads correctly while every conversion attempt fails — demonstrating why this chapter's own warn-box treats the two fixes as genuinely separate.

📄 View solution

Chapter 8 Quick Reference — Course Complete

  • Three mounting options — subdomain, path-mounted reverse proxy (built here), or a full Astro-frontend rewrite
  • --base-href /romaji/ — Angular's own verified flag for correcting built asset references, the real equivalent of Vite's base option
  • A new "mounted" configuration — a minimal fileReplacements override, composed with "production" via Angular's own real, verified comma-separated multi-configuration syntax
  • environment.mounted.ts — sets apiBaseUrl to /romaji without touching Chapter 7's own generic, empty default
  • nginx's trailing-slash prefix-stripping — lets server.js stay completely unchanged from Chapter 7
  • The real payoff — a live link from the existing kanji tiles page and Japanese-language lessons into a genuinely working /romaji/ tool, the exact promise Chapter 1 opened with
  • This closes the course — 8/8 chapters, matching the shared Personal Multi-Technology Projects romaji-to-kana spec from Chapter 1