Bilingual (Japanese/English) corporate website for eSolia Inc., an IT consulting and outsourcing company headquartered in Tokyo, Japan. Built with SvelteKit 2 and deployed on Cloudflare Workers.
Live: esolia.co.jp | Preview: esolia-2025.esolia.workers.dev
graph TB
subgraph "Cloudflare Edge"
W["Workers Runtime<br/>(Smart Placement)"]
A["Workers Assets<br/>(Static Files)"]
D1["D1 Database<br/>(Search Intelligence)"]
CF["Cloudflare Pro<br/>(WAF, DDoS, Image Resizing)"]
end
subgraph "Scheduled Workers"
IDX["esolia-indexing<br/>(Daily: Google + Bing IndexNow)"]
GSC["search-console-ingest<br/>(Daily: GSC Data → D1)"]
GAP["gap-detector<br/>(Weekly: Content Gap Analysis)"]
end
subgraph "External Services"
FA["Fathom Analytics"]
PRO["PROdb (TeamDesk)<br/>(Project/Company Data)"]
GCSA["Google Search Console API"]
TUR["Cloudflare Turnstile"]
end
subgraph "Build Pipeline"
GH["GitHub (main branch)"]
CI["GitHub Actions CI"]
VB["Vite + SvelteKit Build"]
end
GH -->|push| CI
CI -->|deploy| VB
VB -->|output| W
VB -->|static| A
W --> D1
GSC --> GCSA
GSC --> D1
GAP --> D1
IDX -->|IndexNow| GCSA
W -->|page views| FA
PRO -->|nightly refresh| GH
| Layer | Technology |
|---|---|
| Framework | SvelteKit 2 + Svelte 5 (runes API) |
| Language | TypeScript (strict mode) |
| Styling | Tailwind CSS v4 |
| Fonts | IBM Plex Sans, IBM Plex Sans JP, IBM Plex Mono |
| Icons | phosphor-svelte |
| Content | Markdown + unified (remark/rehype) pipeline |
| Validation | Zod (frontmatter) |
| Search | MiniSearch (client-side) |
| Analytics | Fathom |
| Deployment | Cloudflare Workers + Assets |
| Database | Cloudflare D1 |
| CI/CD | GitHub Actions |
| Package Manager | pnpm 10 |
| Node | >= 22 |
130 bilingual pages (65 JA + 65 EN) and 6 articles (3 per language) covering:
- Services — IT consulting, infrastructure, outsourcing, security, training, and more
- Solutions — Periodic (asset management), Pulse (monitoring), PROdb (cloud database)
- About — Company overview, team, philosophy, CSR, employment
- Resources — Japan-specific guides (emergency contacts, apps, J-Alert)
- Topics — B2B IT in Japan, bilingual liaison, cloud infrastructure, security compliance
- Articles — In-depth guides and thought leadership
Content lives in content/{lang}/pages/ and content/{lang}/articles/ as Markdown with Zod-validated YAML frontmatter.
pnpm install # Install dependencies
pnpm run dev # Dev server (localhost:5173)
pnpm run verify # Lint + type-check + test (run before commits)
pnpm run build # Production build (auto-fetches PROdb data + search index)
pnpm run preview # Preview production build locallyDual-layer system: oxlint (fast pass) then eslint (Svelte + 4 custom backpressure rules):
esolia/no-raw-html—{@html}must use sanitize wrapperesolia/no-binding-leak— don't returnplatform.envfrom loadesolia/no-schema-parse— prefersafeParse()over.parse()esolia/no-silent-catch— catch blocks must not swallow errors
graph LR
PRO["PROdb API<br/>(TeamDesk)"] -->|fetch-prodb.mts| JSON["content/data/prodb/*.json"]
JSON -->|import.meta.glob| BUILD["Vite Build"]
MD["Markdown Files"] -->|unified pipeline| BUILD
BUILD -->|adapter-cloudflare| WORKER["_worker.js"]
style PRO fill:#f9f,stroke:#333
style WORKER fill:#ff9,stroke:#333
PROdb data (projects, holidays, contacts) is fetched at build time and embedded via import.meta.glob. A nightly GitHub Actions workflow refreshes the cached JSON and pushes to main, triggering a Cloudflare rebuild.
| Worker | Schedule | Purpose |
|---|---|---|
| esolia-2025 (main) | On request | SvelteKit site + 404 logging to D1 |
| esolia-contact-form | On request | Contact form with Turnstile + PROdb |
| esolia-indexing | Daily 3 AM JST | Submit URLs to Google + Bing IndexNow |
| esolia-search-console-ingest | Daily 3 PM JST | Fetch GSC data into D1 |
| esolia-gap-detector | Weekly Mon 4 PM JST | Analyze content gaps from search data |
Japanese is the default language. English routes live under /en/.
graph TD
REQ["Incoming Request"] -->|"/"| DETECT{"Language Detection<br/>(cookie → Accept-Language)"}
DETECT -->|Japanese| JA["/ (Japanese Homepage)"]
DETECT -->|English| REDIR["302 → /en/"]
REDIR --> EN["EN Homepage"]
REQ -->|"/en/*"| EN
REQ -->|"/services/*"| JA_PAGE["JA Content Page"]
REQ -->|"/en/services/*"| EN_PAGE["EN Content Page"]
src/
├── app.css # Tailwind v4 config
├── hooks.server.ts # Language detection, security headers, 404 logging
├── lib/
│ ├── components/ # Svelte 5 components (layouts, homepage, demos)
│ ├── content/ # Content pipeline (loader, frontmatter, TOC, schema)
│ ├── i18n/ # Language detection
│ └── server/ # Server-only (PROdb data)
├── routes/ # SvelteKit routes (JA default + /en/ mirror)
└── _data/ # YAML config (sidebars, i18n strings)
content/ # Markdown content (outside src/)
├── {en,ja}/pages/ # Bilingual page content
├── {en,ja}/articles/ # Bilingual articles
└── data/prodb/ # Cached PROdb JSON
workers/ # Standalone Cloudflare Workers
├── contact-form/ # Form handler
├── indexing/ # Search engine indexing
├── search-console-ingest/ # GSC data pipeline
└── gap-detector/ # Content gap analysis
static/ # Static assets (favicons, images)
| Workflow | Trigger | Steps |
|---|---|---|
| CI | Push, PR | format:check, lint, svelte-check, vitest |
| Nightly Data Refresh | Cron 3 AM JST | Fetch PROdb → commit if changed → push |
| IndexNow | Push to main | Detect changed URLs → submit to search engines + Fathom milestone |
Pushes to main trigger automatic Cloudflare Workers builds.
See SECURITY.md for vulnerability reporting and security practices.