React/Vite website for ZhStudio, focused exclusively on professional websites and webdesign.
- Node.js
- npm
npm installnpm run devThen open the local URL shown in the terminal, usually:
http://localhost:5173
npm run buildThe built files are generated in dist/.
The Git-connected production site deploys to Cloudflare Pages. A small Pages middleware reuses the Worker routing logic for canonical redirects, security headers and real 404 responses. The same app can also be deployed directly as a Cloudflare Worker with static assets.
Cloudflare build settings:
Build command: npm run build
Deploy command: npx wrangler deploy
Root directory: /
Build output directory: dist
Useful local commands:
npm run worker:dev
npm run deployThe Worker serves the built Vite app from dist/. The build prerenders every public React route with its complete content and route-specific metadata and returns a real 404 for unknown paths. API routes can be added under /api/*; /api/request-info already returns basic request and Cloudflare edge metadata.
src/App.jsx- page content, routing, sections, and animationssrc/styles.css- complete visual styling and responsive layoutsrc/worker.js- Cloudflare Worker entry for API routespublic/- public assets and static filesindex.html- metadata, fonts, app mount
Run npm test to build and verify the complete static HTML, blocking CSS, metadata, reference links and sitemap rules. The build-time React entry is src/entry-server.jsx; the browser hydrates its markup without replacing the page. No server-side runtime is needed in production.
German retains the existing URLs. French uses /fr, /fr/prestations, /fr/contact, /fr/mentions-legales, /fr/confidentialite, /fr/merci and /fr/404.
src/locales/fr.jscontains the French copy;src/languages.jsmaps equivalent routes.- French is auto-selected from the primary browser language only on first entry to
/in a tab. A sessionStorage marker prevents further automatic redirects in that tab: manually removing/fropens German. Explicit subpage URLs always win, internal links retain the current language, and a new tab can detect French again. Former localStorage preferences are cleared.?lang=de|frremains an explicit URL override. There is no visible language switch. - All internal links retain the current locale. Both language versions are directly accessible by URL, including without JavaScript.
- Each indexable page has a self-canonical, reciprocal DE/FR/x-default alternates and a sitemap entry. Thank-you and error pages remain noindex.
scripts/sitemap.mjsgenerates the production sitemap from route metadata. Keeppublic/sitemap.xmlsynchronized for development.node scripts/create-og.mjs --frregenerates the French social preview image.