senternet-site-vite-setup
Scaffold a production-ready Vite + React + TypeScript site.
Files1
View on GitHubSKILL.md.claude/skills/senternet-site-vite-setup/SKILL.md
GitHubVite + React + TypeScript Site Setup
Initialize a production-ready Vite + React + TypeScript project for a marketing site.
This is the vite track — static output on Firebase Hosting, with Puppeteer prerendering for crawlers. It is the default track and the right one for most marketing sites. If the site needs server rendering, route handlers, middleware, or on-demand regeneration, use /senternet-site-nextjs-setup instead. /senternet-site-framework has the decision guidance and the convention map between the two.
Steps
Ask the user which local dev port to use. Default is
3000. Explain that they may want a different port if they run multiple sites locally (e.g. 3001, 3002, 3003). Use their answer as$PORTthroughout the rest of the setup.Scaffold the project with Vite using the React + TypeScript template:
npm create vite@latest $SITE_NAME -- --template react-ts cd $SITE_NAME npm installReplace
$SITE_NAMEwith the project directory name (e.g.myapp-site).Install core dependencies:
npm install react-router-dom npm install -D puppeteer sharpUpdate
vite.config.tswith:- Path alias
@/→src/ outDir: 'build'(not the defaultdist— Firebase and prerender scripts expectbuild/)manualChunks: { 'vendor-react': ['react', 'react-dom', 'react-router-dom'] }for parallel chunk loadingbuild.target: 'esnext'- Dev server port from
PORTenv var with the user-chosen default ($PORT) - An
htmlPluginthat rewritesVITE_BASE_URLinto meta tagcontentand canonicalhrefattributes at build time, and conditionally injects or strips the GA block based onVITE_GA_ID
The htmlPlugin should also inject
<link rel="modulepreload">tags for all JS chunks so the browser fetches them from<head>rather than waiting for the<script>at end of body.- Path alias
Set up
src/main.tsx:- Use
hydrateRoot(notcreateRoot) so React attaches to pre-rendered DOM without replacing it — this keeps LCP from the initial HTML paint - Dispatch
new Event('app-ready')after hydration so the prerender script knows the page is ready - Add a 1s fallback timeout that also dispatches
app-ready
- Use
Set up
src/App.tsxwith React Router:- Use
BrowserRouter+Routes+Route - Add a catch-all
<Route path="*">that redirects to/ - Lazy-load page components with
React.lazy+Suspense - Add a
ScrollToTopcomponent that resets scroll on navigation, and render it insideBrowserRouter(aboveRoutes). Without this, React Router preserves the scroll position across route changes — so clicking a link while scrolled to the bottom of one page lands the user partway down the next page. This is a consistent, easy-to-miss bug on new sites, so it must always be wired up.
Note: it must live insideimport { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; function ScrollToTop() { const { pathname } = useLocation(); useEffect(() => { window.scrollTo(0, 0); }, [pathname]); return null; }<BrowserRouter>because it depends onuseLocation. If the site uses hash links for in-page anchors, key the effect onpathnameonly (nothash) so anchor jumps still work.
- Use
Create
.env.developmentand.env.productionfiles:VITE_BASE_URL=http://localhost:$PORT VITE_GA_ID=and
VITE_BASE_URL=https://www.yoursite.com VITE_GA_ID=G-XXXXXXXXXXIf the repo already has a non-empty
VITE_GA_IDin.env.production, preserve it during upfit runs and do not overwrite it with the placeholder. Only useG-XXXXXXXXXXas the initial scaffold value when no Measurement ID has been established yet.Update
package.jsonscripts:{ "dev": "vite --port $PORT", "build": "tsc -b && vite build", "build:prod": "tsc -b && vite build --mode production && node scripts/prerender.mjs", "preview": "vite preview" }Add
tsconfig.jsonwith strict mode and path alias for@/.Create
src/index.csswith Tailwind CSS v4 import:
@import "tailwindcss";
Install Tailwind:
npm install -D tailwindcss @tailwindcss/vite
Add @tailwindcss/vite to the vite plugins array.
Create
scripts/directory for build helpers.Create
public/withfavicon.png,favicon.svg,favicon.ico, andapple-touch-icon.pngplaceholders.Create
src/assets/for optimized images.Write
.site-framework.jsonin the project root so every later skill and every upfit run knows which track this site is on without re-deriving it:{ "framework": "vite", "renderMode": "static-prerender", "host": "firebase-hosting" }
Output
When complete, the project should:
- Run
npm run devwithout errors - Build with
npm run buildwithout TypeScript errors - Have the
@/alias working for imports - Have React Router wired up with at least a home route
- Be ready for Firebase Hosting, GA, prerender, and all other site skills to be layered on top