Marketing site for MoonSofts. React 18, TypeScript, Vite, Tailwind CSS.
Site images are served from Vercel Blob (not committed to Git).
npm install
cp .env.example .env
npm run devOpen http://localhost:3000.
npm run dev runs a soft sync first so public/brand/logo.png exists for the tab favicon when possible (local src/assets/brand/logo.png or VITE_CDN_BASE_URL in .env). If that step warns and skips, add assets or CDN URL, or run npm run cdn:prepare.
| File | Purpose |
|---|---|
.env |
Local dev |
.env.production |
Production build (not committed) |
.env.local |
Secrets, e.g. BLOB_READ_WRITE_TOKEN (not committed) |
Required for builds:
VITE_CDN_BASE_URL=https://pPIvg8AEUNbGNW4T.public.blob.vercel-storage.comCopy from .env.example or .env.production.example if needed.
- Route titles & descriptions live in
src/lib/routeMeta.ts(resolvePageSeo). They are applied on every navigation viaDocumentTitle(meta description, Open Graph, Twitter cards,link rel="canonical",hreflang, and JSON-LD for Organization / WebSite / WebPage, plusNewsArticleon news posts). - No
meta name="keywords": search engines ignore it; important phrases belong in visible headings and body copy.DocumentTitlestrips any legacy keywords tag on route change. - Breadcrumbs in structured data: inner routes append a
BreadcrumbListgraph (seesrc/lib/seoBreadcrumbs.ts) alongside the main JSON-LD block. - NewsArticle dates: when a news item has a parseable display date,
datePublished/dateModifiedare emitted for richer article snippets (src/lib/newsDate.ts). - Set
VITE_SITE_URL(no trailing slash) in.env.productionso canonicals and structured data use your public domain instead of only the runtime origin. public/robots.txtandpublic/sitemap.xmlusehttps://moonsofts.netas the default host; update both if your live domain differs (or automate sitemap generation later).
High-resolution PNGs live on Vercel Blob. Paths are defined in src/lib/siteImages.cdn.ts.
First-time upload
- Put originals under
src/assets/(seescripts/cdn-asset-manifest.json). npm run cdn:prepare→ createscdn-upload/.- Add
BLOB_READ_WRITE_TOKENto.env.local(from Vercel → Storage → Blob store). npm run vercel:blob:upload- Confirm a URL works, e.g.
https://pPIvg8AEUNbGNW4T.public.blob.vercel-storage.com/brand/moonsofts-logo.png
Re-upload after changing images: repeat steps 2–4.
| Command | Description |
|---|---|
npm run dev |
Dev server |
npm run build |
Production build (requires VITE_CDN_BASE_URL) |
npm run preview |
Preview production build |
npm run lint |
ESLint |
npm run cdn:prepare |
Build cdn-upload/ from src/assets/ |
npm run cdn:manifest |
List CDN paths and URLs |
npm run vercel:blob:upload |
Upload cdn-upload/ to Vercel Blob |
- Set project Root Directory to
frontend. - Add env var:
VITE_CDN_BASE_URL= your Blob public base URL (same as above). - Build command:
npm run build
Output:dist
src/assets/**/*.png and cdn-upload/ are gitignored so pushes stay small. Only application code is in the repo.
The browser tab icon is the company logo.png, copied to public/favicon.png and public/favicon.ico on dev/build (scripts/sync-public-brand-logo.mjs). index.html references /favicon.png. Run npm run cdn:prepare when you add or change the logo under src/assets/brand/.
src/
components/ UI sections, layout, shared components
pages/ Route pages
lib/ Data, CDN helpers, siteImages
layouts/ MainLayout, nav, footer