Skip to content

Stack

The LIFF (LINE Front-end Framework) app is a React SPA delivered as static assets by a Cloudflare Worker.

Stack at a glance

LayerChoicePinned
BundlerVite 5^5.4.x
FrameworkReact 18^18.3.x
Routingreact-router-dom 6^6.30.x
StylingTailwind CSS 3^3.4.x
LINE SDK@line/liff v2^2.x
Iconslucide-react^0.x
Markdownmarked (for legal docs)^x
DeployWrangler 4^4.x
HostingCloudflare Workers Static Assets
LanguageTypeScript strict~5.x

Project layout

liff/src/
├── App.tsx Top-level router + AuthGate
├── main.tsx React entry
├── index.css Tailwind + custom keyframes
├── api/ typed HTTP client per resource
│ ├── auth.ts
│ ├── users.ts
│ ├── foodLogs.ts
│ ├── weightLogs.ts
│ ├── chat.ts
│ ├── billing.ts
│ └── account.ts
├── assets/ inline imports (mascot, etc.)
├── components/
│ ├── AppShell.tsx header + main + bottom nav
│ ├── AuthLoadingScreen.tsx
│ ├── OnboardingSplash.tsx
│ ├── ProfileForm.tsx
│ ├── Dashboard sections (PremiumSection, ChatSection, etc.)
│ ├── KcalRing.tsx
│ ├── WeightChart.tsx
│ └── LegalPage.tsx
├── hooks/
├── i18n/
├── lib/
│ ├── api.ts fetch wrapper + session token
│ ├── env.ts VITE_* env vars
│ └── premium.ts isPremium mirror
├── pages/ route components
│ ├── DashboardPage.tsx
│ ├── ProfilePage.tsx
│ ├── PremiumPage.tsx
│ ├── ChatPage.tsx
│ ├── SettingsPage.tsx
│ ├── SupportPage.tsx
│ └── NotFoundPage.tsx
├── state/
│ └── session.ts User + streak + premium
├── styles/ (rarely used; Tailwind is primary)
└── types/ shared types mirroring backend
├── user.ts
├── foodLog.ts
├── weightLog.ts
├── chatMessage.ts
└── billing.ts

Deploy pipeline

  1. git push origin main → Cloudflare Workers Builds detects change in projects/liff/
  2. Build command: cd liff && npm install && npm run build
  3. Output: liff/dist/
  4. Deploy command: cd liff && npx wrangler deploy
  5. Worker tinadiet-liff serves from dist/ with SPA fallback

wrangler.toml essentials

name = "tinadiet-liff"
compatibility_date = "2024-XX-XX"
[assets]
directory = "./dist"
not_found_handling = "single-page-application"

The not_found_handling = "single-page-application" is critical — it makes ANY path return index.html so react-router can handle client-side routing (essential for /privacy, /premium, /profile, etc.).

Do NOT use Cloudflare Pages-style _redirects file — Workers rejects it with error 100324. See feedback memory feedback-cloudflare-workers-vs-pages-redirects.

Environment variables

Vite reads VITE_* prefixed env vars at BUILD time, embedding them in the bundle:

VarPurpose
VITE_API_BASE_URLBackend URL — https://api.tinadiet.com in prod
VITE_LIFF_IDLIFF channel ID from LINE Developers Console

Plus NODE_VERSION=22 set in Cloudflare Workers Builds dashboard (required since Sprint 3 wrangler 4 upgrade).

Why no SSR / Next.js / Remix?

  • LIFF runs inside LINE’s webview — there’s no SEO benefit to SSR (LINE doesn’t crawl)
  • LIFF SDK init is client-side anyway
  • Cloudflare Workers Static Assets has very low cold start (no Node runtime needed)
  • Vite + React = simple, fast dev iteration, mature ecosystem

If SEO of marketing pages becomes important, that’s a separate site (this docs site already separates) — not a reason to add SSR to LIFF.