A lightweight, Angular‑based tech blog. Built with Angular (Material 3), deployable on Vercel as a static SPA (no SSR). Includes clean environment management, API proxying for local dev, and production rewrites to avoid mixed‑content issues.
- Angular + Angular Material (MD3) with CSS custom properties
- Clean separation of local and production API endpoints
- Local dev proxy to bypass CORS while developing
- Vercel rewrites to avoid browser mixed‑content (HTTPS → HTTP) issues
- SPA routing fallback for Vercel
- Build budget guidance and bundle analysis
- Angular (v18+)
- Angular Material 3 (theming via CSS vars)
- RxJS
- Vercel (static SPA deployment)
No SSR by default. If later needed, migrate to Angular SSR/Prerender.
- Node.js 18+ (recommend LTS)
- npm (or pnpm/yarn)
npm ci # or: npm install# uses proxy.conf.json for /api → your backend
npm run startnpm run buildThe production build is emitted to dist/hobom-tech-blog/.
- Connect the repo in Vercel dashboard
- Framework preset: Angular
- Build command:
npm run build - Output directory:
dist/hobom-tech-blog - Include
vercel.jsonat repo root (see below)
src/
app/
assets/
icons/
environments/
environment.ts
environment.prod.ts
favicon.ico
index.html
angular.json
proxy.conf.json
vercel.json
Create src/environments/environment.ts and src/environments/environment.prod.ts:
src/environments/environment.ts
export const environment = {
production: false,
// Local dev calls should go through the proxy: use relative /api
apiBaseUrl: '/api/hobom/tech',
};*A simple service example:*c
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from '../environments/environment';
@Injectable({ providedIn: 'root' })
export class PostsService {
constructor(private http: HttpClient) {}
list(limit = 20) {
return this.http.get(`${environment.apiBaseUrl}/articles`, { params: { limit } });
}
}Use a proxy to call your backend over HTTP during ng serve without CORS errors:
proxy.conf.json
{
"/api": {
"target": "http://ishisha.iptime.org:8081/hobom-internal/api/v1",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/api": ""
},
"logLevel": "debug"
}
}angular.json (serve options)
{
"projects": {
"hobom-tech-blog": {
"architect": {
"serve": {
"options": {
"proxyConfig": "proxy.conf.json"
}
}
}
}
}
}Run dev:
ng serve --proxy-config proxy.conf.jsonIn production, browsers require HTTPS for pages served over HTTPS. If your backend is HTTP only, route through Vercel so the browser calls same‑origin HTTPS and Vercel calls your backend server‑to‑server.
vercel.json (at repo root)
{
"rewrites": [
{ "source": "/api/(.*)", "destination": "http://ishisha.iptime.org:8081/hobom-internal/api/v1/$1" },
{ "source": "/(.*)", "destination": "/index.html" }
],
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }
]
}
]
}This eliminates Mixed Content because the browser only talks to
https://hobom-tech-blog.vercel.app.
If you have an HTTPS API domain, you can instead set environment.prod.ts to point to it directly and remove the rewrite for /api.
- Replace
src/favicon.icowith your icon or update the link insrc/index.html:
<link rel="icon" type="image/x-icon" href="assets/icons/hobom.ico" />- Add social share images under
src/assets/(e.g.,og-image.png).
Angular Material 3 exposes CSS vars like --mat-sys-surface. To apply a light gray background site‑wide:
:root {
/* Optional: tweak tonal surfaces */
/* --mat-sys-surface: #f7f7f7; */
}
html, body, app-root { height: 100%; }
body { margin: 0; background: var(--mat-sys-surface); }
/* Sometimes using container surfaces feels nicer */
.app-surface {
background: var(--mat-sys-surface-container-low);
}For advanced theming, define a custom Material theme and load via
@usein your global styles.
ng build --configuration productionng build --configuration production --stats-json
npx source-map-explorer 'dist/hobom-tech-blog/**/*.js' --html dist/report.htmlEdit angular.json → projects.hobom-tech-blog.architect.build.configurations.production.budgets:
"budgets": [
{ "type": "initial", "maximumError": "1.5mb", "maximumWarning": "1.0mb" },
{ "type": "anyComponentStyle", "maximumWarning": "6kb", "maximumError": "10kb" }
]Prefer reducing bundle size (code‑splitting, removing heavy libs, using
standalonecomponents, lazy routes) rather than only increasing budgets.
For client‑side routes (e.g., /articles/123), Vercel must serve index.html and let Angular router handle it. The vercel.json rewrite {"source": "/(.*)", "destination": "/index.html"} ensures this.
{
"scripts": {
"start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build --configuration production",
"analyze": "ng build --configuration production --stats-json && npx source-map-explorer 'dist/hobom-tech-blog/**/*.js'",
"lint": "ng lint",
"test": "ng test"
}
}- Mixed Content error: Ensure API calls use
/api/...and Vercel rewrites to your HTTP backend. Or use a proper HTTPS backend. - 404 on hard refresh: Ensure the SPA fallback rewrite to
/index.htmlexists invercel.json. - Favicon not updating: Bust cache by renaming the file (e.g.,
favicon-v2.ico) and updatingindex.html, or hard‑reload the browser. - CORS in local dev: You must run via
ng servewithproxy.conf.json, and call the API via relative/api/...URLs. - Large bundles: Use lazy loading, remove unused polyfills, and analyze with
source-map-explorer.
MIT (c) HoBom