# Teknav Improvement Plan — Performance, SEO depth, Trust ## Context Teknav has a mature, high-performing SEO/content engine (an article ranks #1 on Google above Wikipedia). The remaining growth leverage is **not** more features — it's (1) shipping far less JavaScript to readers, (2) conservative, additive SEO depth that won't risk current rankings, and (3) light trust/footer polish on top of footer links the user already added. Investigation confirmed the core problem: the reader SPA ships **one undivided app bundle**. `vite.config.js:42` `manualChunks` only splits `node_modules`. There is **zero** route- or component-level splitting (`React.lazy`/`Suspense` appear nowhere). Concretely: - `teknav-diagrams.jsx` imports `three` and is **statically imported by every page** (`teknav-home.jsx:5`, `teknav-articles.jsx:9`, `teknav-pages.jsx:14`, `teknav-en.jsx:5`, `teknav-admin.jsx:9`) → three.js downloads on the homepage for every visitor. - `src/components/knowledge-graph.jsx` imports `d3` (also heavy). - `teknav-admin.jsx` (full CMS + editor) is statically imported in `teknav-app.jsx:13` → admin code ships to every reader. Goal: cut reader bundle weight (better LCP on Iranian mobile networks → better engagement and rankings) without touching the zero-CDN rule, RTL, or the static-prerender pipeline. Sequencing decision (from user): **measure first**. Web push is **out of scope**. SEO changes must be additive and non-destructive. --- ## Phase 0 — Baseline measurement (do first, no code changes) Establish numbers so we fix what hurts and can prove the gain. - Build prod: `npm run build`, then `npm run preview` (port 4173). - Bundle baseline: inspect `dist/assets/*.js` sizes (sourcemaps already on, `vite.config.js:26`); record entry size and which chunks contain `three`/`d3`/admin. (Optional: add a one-off `rollup-plugin-visualizer` run locally — do **not** commit it.) - Lighthouse (mobile, throttled) on `/`, a `/article/`, and `/` again warm — capture LCP, TBT, total JS transfer. - Record results at top of this file as the "before" row. **Gate:** the splitting work below targets the chunks Phase 0 proves are largest. If `three`/admin are not in the entry, re-scope. --- ## Phase 1 — Performance / bundle (primary work) ### 1a. Lazy-load the heavy visualization components Create thin lazy wrappers so the 5 existing import sites change minimally and `three`/`d3` move into on-demand chunks: - New `src/components/lazy-diagram.jsx`: ```jsx import { lazy, Suspense } from 'react'; const DiagramRenderer = lazy(() => import('./teknav-diagrams.jsx').then(m => ({ default: m.DiagramRenderer }))); export function LazyDiagram(props) { return ; } ``` (Note: `teknav-diagrams.jsx` exports `DiagramRenderer` as a **named** export — wrapper maps it to `default`.) - Replace the 5 static `import { DiagramRenderer }` sites (`teknav-home.jsx`, `teknav-articles.jsx`, `teknav-pages.jsx`, `teknav-en.jsx`, `teknav-admin.jsx`) with `LazyDiagram`. - Same pattern for `knowledge-graph.jsx` (d3) wherever it's mounted. - Add a small `.tek-diagram-skeleton` style to `src/styles/global.css` reusing existing tokens (no new animation primitives — reuse the shared keyframes per CLAUDE.md). ### 1b. Route-split admin/editor out of the reader path - In `teknav-app.jsx`, convert the heavy, reader-irrelevant routes to `React.lazy`: `AdminPanel`, `WriterWorkspace` (both from `teknav-admin.jsx:13`), and `LoginPage`. Keep reader-critical pages (`HomePage`, `ArticleDetail`) eagerly imported to avoid hydration jank. - Wrap `
{renderPage()}
` (`teknav-app.jsx:195`) in a single ``. - This removes the entire CMS/editor graph from the default reader download. ### 1c. manualChunks hygiene - Extend `manualChunks` (`vite.config.js:42`) so `three` and `d3` get their own named chunks (`vendor-three`, `vendor-d3`). Once 1a makes them dynamic imports, Rollup splits them automatically; the named chunks just keep caching stable across deploys. ### 1d. Below-the-fold deferral on article page (measurement-gated) - In `teknav-articles.jsx`, lazy-mount non-critical widgets (comments, Q&A, related, semantic suggestions) so they don't block first render. Only do the ones Phase 0 shows are costly. ### 1e. Font delivery (small, safe) - Audit `@font-face` in `src/styles/global.css`: ensure `font-display: swap` and `` for the primary Vazirmatn/PelakFA weights only. Self-hosted already (zero-CDN preserved). No subsetting unless Phase 0 flags fonts as a major cost. --- ## Phase 2 — SEO depth (conservative, additive only) Premise from user: SEO already works — only add what helps and **cannot** ruin current results. So: no changes to existing article HTML, schema types, canonical/hreflang rules, or sitemap structure (all already correct; stories/compare/directory confirmed present in `generate-seo.mjs`). Candidate additive wins (each independently revertible; pick after Phase 0/audit): - **Related-article internal links at the component level** (render-time in `teknav-articles.jsx`), NOT by mutating stored content HTML — strengthens topic-cluster interlinking with zero risk to existing pages. Verify whether a related block already exists before adding. - **Breadcrumb JSON-LD** via `src/lib/seo.jsx` on article/category/glossary pages if not already emitted (purely additive structured data). - Re-run `npm run check:seo` as a gate after any content-adjacent change (CLAUDE.md invariant). - **Explicitly NOT extending `link-engine.ts`** to rewrite published content inline (risk of altering ranked pages). Leave glossary autolinking as-is. If the audit shows nothing safe to add, Phase 2 is intentionally a no-op — that is an acceptable outcome. --- ## Phase 3 — Trust / footer (light) User already added the footer links. Scope here is only: - Verify each footer link resolves in `renderPage` (`teknav-app.jsx:146`) — report any that 404. - Optionally add an **Editorial Guidelines** (اصول تحریریه) page using the existing `LegalPage`/`LegalSection` pattern (`teknav-pages.jsx`) + route, since it's the highest-value missing E-E-A-T page for tech content. Confirm with user before building. --- ## Files to modify (summary) | File | Change | |---|---| | `src/components/lazy-diagram.jsx` (new) | Lazy wrapper for `DiagramRenderer` | | `teknav-home/articles/pages/en/admin.jsx` | Swap static `DiagramRenderer` → `LazyDiagram` | | `src/components/knowledge-graph.jsx` consumers | Lazy-mount d3 graph | | `teknav-app.jsx` | `React.lazy` admin/writer/login + `` wrap | | `vite.config.js` | `vendor-three` / `vendor-d3` manualChunks | | `teknav-articles.jsx` | Defer below-the-fold widgets; optional related-links block | | `src/styles/global.css` | Diagram skeleton style; font-display/preload audit | | `src/lib/seo.jsx` | Breadcrumb JSON-LD (if absent) | | `teknav-pages.jsx` + `teknav-app.jsx` | (Optional) Editorial Guidelines page + route | ## Constraints honored Zero CDN (no new external assets); Persian-first RTL; reuse `global.css` keyframes/utilities; never edit `dist/*` by hand; static-prerender pipeline unchanged (diagrams are already client-rendered, so lazy-loading is no SEO regression). ## Verification (end-to-end) 1. `npm run build` succeeds; `check-no-cdn` and `check-responsive` pass. 2. Confirm split chunks exist: `three`/`d3`/admin no longer in the main entry `dist/assets/*.js` (compare to Phase 0 baseline). 3. `npm run preview`: load `/` and an `/article/` — diagrams still render after hydration; no console errors; Suspense fallbacks behave. 4. Re-run Lighthouse mobile on the same 3 URLs; record "after" row and verify LCP/TBT/JS-transfer improvement. 5. `npm run check:seo` clean; spot-check a prerendered `dist/article//index.html` still has its content + schema. 6. `npm run compress-dist` produces `.gz`/`.br` for the new chunks (runs in pipeline). 7. Visit each footer link; confirm no 404s.