Port portfolio from Vite SPA to TanStack Start with full SSR SEO - #9
Conversation
- Replace react-router-dom/react-helmet-async/vite-aliases/redux with @tanstack/react-start + @tanstack/react-router - Add tanstackStart() vite plugin (before react) with explicit '#' aliases - Serve the SSR build with srvx; update start script + railway.toml - Ignore generated routeTree.gen.ts and build output Co-authored-by: Lakshmikanta Patra <frontendfixer@users.noreply.github.com>
- Add document shell in src/routes/__root.tsx (HeadContent/Scripts, global meta, favicons, manifest, JSON-LD structured data) - Add home route src/routes/index.tsx with per-page SEO head - Add src/lib/seo.ts to build meta/links/JSON-LD from siteConfig (ports the old react-helmet SEO component to server-rendered head) - Add src/router.tsx (getRouter) and move Navigation to components/layout - Provide SSR-safe VITE_SITE_URL fallback - Remove legacy SPA entry files (main, App, Home, old Navigation, SEO, index.html) Co-authored-by: Lakshmikanta Patra <frontendfixer@users.noreply.github.com>
- Adopt TanStack Start's documented Bun production server (server.ts, Bun.serve) which serves dist/client + the dist/server SSR handler; matches our Vite build output and honors PORT - start script -> 'bun run server.ts'; drop srvx dependency - Bump engines (node >=24, bun >=1.3.14; bun 1.3.14 is the latest release) - eslint: ignore standalone server.ts; railway: watch server.ts Co-authored-by: Lakshmikanta Patra <frontendfixer@users.noreply.github.com>
|
Cursor Agent can help with this pull request. Just |
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: edfa84f020
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| gz, | ||
| etag, | ||
| type: metadata.type, | ||
| immutable: true, |
There was a problem hiding this comment.
Avoid immutable caching for unversioned public files
With the default preload settings, every small file in dist/client is marked immutable here, including unversioned public files such as /robots.txt, /sitemap.xml, /site.webmanifest, favicons, and social preview images. createResponseHandler then serves them with a one-year Cache-Control: ... immutable, so updates to SEO metadata, icons, or social images can remain stale in browsers/CDNs for up to a year; reserve immutable caching for hashed build assets such as /assets/* and use a shorter or revalidating policy for root public files.
Useful? React with 👍 / 👎.
| headers['Content-Encoding'] = 'gzip' | ||
| headers['Content-Length'] = String(asset.gz.byteLength) |
There was a problem hiding this comment.
Vary cached gzip responses by Accept-Encoding
When a preloaded JS/CSS asset is requested with Accept-Encoding: gzip, this branch returns gzip bytes but does not add Vary: Accept-Encoding. Because these responses are also public/cacheable, a shared cache or CDN can store the gzip representation and replay it to a client that did not request gzip, producing an unreadable asset response; add the Vary header whenever gzip can be selected.
Useful? React with 👍 / 👎.
Summary
Ports the portfolio site from a client-only Vite + React Router SPA (with
react-helmet-asyncfor SEO) to TanStack Start, so the page is server-side rendered and search engine crawlers / social scrapers receive fully-rendered HTML with all meta tags in the initial response.Why
Previously all SEO tags (
<title>, description, Open Graph, Twitter, JSON-LD) were injected client-side byreact-helmet-async. Crawlers that don't execute JS saw an empty<div id="root">. TanStack Start renders the documentheadon the server via the routehead()API, giving genuine SEO support.Stack changes
react-router-dom(client routing)@tanstack/react-router(file-based routing)react-helmet-async(client meta)head()+HeadContent(SSR meta)index.html+main.tsx)__root.tsxdocument shell)vite-aliases#aliases invite.config.ts@reduxjs/toolkit/react-reduxWhat changed
New TanStack Start structure
src/routes/__root.tsx— document shell (<html>/<head>/<body>,HeadContent,Scripts), global meta (charset, viewport, theme-color), favicons, web manifest, and JSON-LD structured data (Person / WebSite / Organization) rendered on every page.src/routes/index.tsx— home route composing all existing sections, with per-page SEOhead()(title, description, keywords, canonical, Open Graph, Twitter card).src/router.tsx—getRouter()factory.src/lib/seo.ts— ports the oldSEO.tsxhelper logic into TanStackheadmeta/links/scripts objects.src/components/layout/Navigation.tsx— the oldroutes/Navigation.tsx, now a pure header (routeOutletmoved to__root).Runtime / deployment (Bun)
server.ts,Bun.serve), which servesdist/clientstatic assets and forwards other requests to thedist/server/server.jsSSR handler. This is the recommended Bun deployment path for a Vite build (React 19+, which this project uses), and fits the repo's existing Bun toolchain.startscript →bun run server.ts; the intermediatesrvxdependency was removed.Config / versions
vite.config.ts—tanstackStart()plugin (before React), explicit#path aliases replacingvite-aliases.src/config/site.ts— SSR-safe fallback forVITE_SITE_URL(https://frontendfixer.dev).package.json— swapped dependencies;enginesbumped tonode >=24(latest LTS "Krypton") andbun >=1.3.14;packageManagerstaysbun@1.3.14(the latest Bun release); addedtypecheck.railway.toml—startCommand+ watchserver.ts..gitignore/eslint.config.mjs— ignore generatedrouteTree.gen.ts, build output, and the standaloneserver.ts.Removed:
src/main.tsx,src/App.tsx,src/routes/Home.tsx,src/routes/Navigation.tsx,src/components/common/SEO.tsx,index.html.All existing sections, styling (Tailwind v4 theme), assets, and the EmailJS contact form are preserved unchanged.
Verification
bun run devandbun run buildsucceed; SSR output verified viacurl— HTML contains rendered content plus<title>, description, Open Graph, Twitter card, canonical URL and all three JSON-LD schemas.bun run server.tsverified: SSR HTML + hashed/assets/*files + static assets (/sitemap.xml,/robots.txt,/site.webmanifest,/favicon.ico) all return200with correct content types.bun run lint,bun run typecheck, andbun install --frozen-lockfileall pass (matches CI).Notes
v26.5.0(current) /v24.18.0(LTS Krypton); the engine floor is set to the LTS line (>=24). Bun is already at its latest release (1.3.14).node-servertarget (.output) was evaluated but Nitro v3 currently requires Rolldown/Vite 7, so the Bun-native server is used to stay on Vite 6. Static prerendering can be added later once on a Vite 7 / Nitro setup; SSR already delivers full SEO today.bun+ TanStack Start deps installed. Other cloud agents may benefit from an env setup pass at cursor.com/onboard.