From 321ef04b58bdda040049d87dc13e27156a041327 Mon Sep 17 00:00:00 2001 From: donbr <7340008+donbr@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:41:33 -0700 Subject: [PATCH 1/4] perf(routing): lazy-load every route except Home and 404 All routes shipped in one 2.1 MB bundle (678 KB gzipped), so the homepage downloaded ECharts, Cytoscape and Leaflet even though only the visualization pages use them. Projects, Concepts and the six project routes now load with React.lazy inside one Suspense boundary whose fallback keeps the Layout. Homepage script: 275.8 KB (86 KB gzipped). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ppa5DyJCZ1CowKtz58QARN --- src/App.tsx | 66 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 95bb349..fcbc70f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,39 +1,53 @@ -import React from 'react'; +import React, { lazy, Suspense } from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import Layout from '@/components/layout/Layout'; -// Page Components +// Page Components (HomePage and NotFoundPage are small, so they stay in the main bundle) import HomePage from '@/pages/HomePage'; -import ProjectsPage from '@/pages/ProjectsPage'; -import ConceptsPage from '@/pages/ConceptsPage'; import NotFoundPage from '@/pages/NotFoundPage'; +// Lazy-loaded routes: keeps ECharts, Cytoscape and Leaflet out of the homepage bundle +const ProjectsPage = lazy(() => import('@/pages/ProjectsPage')); +const ConceptsPage = lazy(() => import('@/pages/ConceptsPage')); + // Project Components -import GdeltRecordViewer from '@/components/projects/gdelt/GdeltRecordViewer'; // Add .jsx extension -import CytoscapeViewer from '@/components/projects/cytoscape/CytoscapeViewer'; -import EventAnalyzer from '@/components/projects/event-analyzer/EventAnalyzer'; -import SituationalAwareness from '@/components/projects/situational-awareness/SituationalAwareness'; -import GdeltKnowledgeBase from '@/components/projects/gdelt-kb/GdeltKnowledgeBase'; -import AdvancedRetrieval from '@/components/projects/advanced-retrieval/AdvancedRetrieval'; +const GdeltRecordViewer = lazy(() => import('@/components/projects/gdelt/GdeltRecordViewer')); +const CytoscapeViewer = lazy(() => import('@/components/projects/cytoscape/CytoscapeViewer')); +const EventAnalyzer = lazy(() => import('@/components/projects/event-analyzer/EventAnalyzer')); +const SituationalAwareness = lazy(() => import('@/components/projects/situational-awareness/SituationalAwareness')); +const GdeltKnowledgeBase = lazy(() => import('@/components/projects/gdelt-kb/GdeltKnowledgeBase')); +const AdvancedRetrieval = lazy(() => import('@/components/projects/advanced-retrieval/AdvancedRetrieval')); + +// Shown while a lazy route's chunk loads; keeps the nav and footer in place +const RouteFallback: React.FC = () => ( + +
+ Loading… +
+
+); const App: React.FC = () => { return ( - - } /> - } /> - } /> - - {/* Project routes */} - } /> - } /> - } /> - } /> - } /> - } /> - - {/* Catch-all route for 404s */} - } /> - + }> + + } /> + } /> + } /> + + {/* Project routes */} + } /> + } /> + } /> + } /> + } /> + } /> + + {/* Catch-all route for 404s */} + } /> + + ); }; From 6027a0c42261665f3fa90662f8f3ab71eb2190b4 Mon Sep 17 00:00:00 2001 From: donbr <7340008+donbr@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:41:33 -0700 Subject: [PATCH 2/4] feat(meta): give every route its own document title All routes showed the same tab title, so bookmarks and history entries for a case study were indistinguishable from the homepage. Each page now renders a ("Page Name | Don Branson") that React 19 hoists into <head>; the static title in index.html remains the no-JS fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ppa5DyJCZ1CowKtz58QARN --- src/components/projects/advanced-retrieval/AdvancedRetrieval.tsx | 1 + src/components/projects/cytoscape/CytoscapeViewer.tsx | 1 + src/components/projects/event-analyzer/EventAnalyzer.tsx | 1 + src/components/projects/gdelt-kb/GdeltKnowledgeBase.tsx | 1 + src/components/projects/gdelt/GdeltRecordViewer.tsx | 1 + .../projects/situational-awareness/SituationalAwareness.tsx | 1 + src/pages/ConceptsPage.tsx | 1 + src/pages/HomePage.tsx | 1 + src/pages/NotFoundPage.tsx | 1 + src/pages/ProjectsPage.tsx | 1 + 10 files changed, 10 insertions(+) diff --git a/src/components/projects/advanced-retrieval/AdvancedRetrieval.tsx b/src/components/projects/advanced-retrieval/AdvancedRetrieval.tsx index d5916c9..20c65cf 100644 --- a/src/components/projects/advanced-retrieval/AdvancedRetrieval.tsx +++ b/src/components/projects/advanced-retrieval/AdvancedRetrieval.tsx @@ -5,6 +5,7 @@ import { Github, BookOpen } from 'lucide-react'; const AdvancedRetrieval: React.FC = () => { return ( <Layout> + <title>Advanced Retrieval Strategies for RAG | Don Branson {/* Hero Section */}
diff --git a/src/components/projects/cytoscape/CytoscapeViewer.tsx b/src/components/projects/cytoscape/CytoscapeViewer.tsx index a3aa5ed..48bf76a 100644 --- a/src/components/projects/cytoscape/CytoscapeViewer.tsx +++ b/src/components/projects/cytoscape/CytoscapeViewer.tsx @@ -205,6 +205,7 @@ const CytoscapeViewer: React.FC = () => { return ( + STRING Network Viewer | Don Branson

STRING Network Viewer

diff --git a/src/components/projects/event-analyzer/EventAnalyzer.tsx b/src/components/projects/event-analyzer/EventAnalyzer.tsx index 1ac6f5c..3b35e42 100644 --- a/src/components/projects/event-analyzer/EventAnalyzer.tsx +++ b/src/components/projects/event-analyzer/EventAnalyzer.tsx @@ -235,6 +235,7 @@ const EventAnalyzer: React.FC = () => { return ( + Event Analysis System | Don Branson

Event Analysis System

diff --git a/src/components/projects/gdelt-kb/GdeltKnowledgeBase.tsx b/src/components/projects/gdelt-kb/GdeltKnowledgeBase.tsx index 44e6367..912532a 100644 --- a/src/components/projects/gdelt-kb/GdeltKnowledgeBase.tsx +++ b/src/components/projects/gdelt-kb/GdeltKnowledgeBase.tsx @@ -5,6 +5,7 @@ import { ExternalLink, Github, Database } from 'lucide-react'; const GdeltKnowledgeBase: React.FC = () => { return ( + GDELT Knowledge Base | Don Branson {/* Hero Section */}

diff --git a/src/components/projects/gdelt/GdeltRecordViewer.tsx b/src/components/projects/gdelt/GdeltRecordViewer.tsx index 6779918..617da77 100644 --- a/src/components/projects/gdelt/GdeltRecordViewer.tsx +++ b/src/components/projects/gdelt/GdeltRecordViewer.tsx @@ -355,6 +355,7 @@ const GdeltRecordViewer: React.FC = () => { return ( + GDELT GKG Viewer | Don Branson

GDELT GKG Viewer

diff --git a/src/components/projects/situational-awareness/SituationalAwareness.tsx b/src/components/projects/situational-awareness/SituationalAwareness.tsx index 639de5f..df22c7f 100644 --- a/src/components/projects/situational-awareness/SituationalAwareness.tsx +++ b/src/components/projects/situational-awareness/SituationalAwareness.tsx @@ -106,6 +106,7 @@ const SituationalAwareness: React.FC = () => { return ( + Situational Awareness Graph | Don Branson

Situational Awareness Graph

diff --git a/src/pages/ConceptsPage.tsx b/src/pages/ConceptsPage.tsx index 822051c..96a1473 100644 --- a/src/pages/ConceptsPage.tsx +++ b/src/pages/ConceptsPage.tsx @@ -4,6 +4,7 @@ import Layout from '@/components/layout/Layout'; const ConceptsPage: React.FC = () => { return ( + MCP Architecture: Concepts Before Code | Don Branson {/* Hero Section */}
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index a07b56b..cbac4b3 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -24,6 +24,7 @@ const HomePage: React.FC = () => { return ( + Don Branson | Generative AI Architect & Systems Engineer {/* Hero Section */}
diff --git a/src/pages/NotFoundPage.tsx b/src/pages/NotFoundPage.tsx index e17c7e0..85a6f18 100644 --- a/src/pages/NotFoundPage.tsx +++ b/src/pages/NotFoundPage.tsx @@ -5,6 +5,7 @@ import Layout from '@/components/layout/Layout'; const NotFoundPage: React.FC = () => { return ( + Page Not Found | Don Branson

404 - Page Not Found

The page you are looking for doesn't exist or has been moved.

diff --git a/src/pages/ProjectsPage.tsx b/src/pages/ProjectsPage.tsx index f7f0e76..e0721f8 100644 --- a/src/pages/ProjectsPage.tsx +++ b/src/pages/ProjectsPage.tsx @@ -243,6 +243,7 @@ const ProjectsPage: React.FC = () => { return ( + Projects | Don Branson
From 4d570d6771cbba380e40ec657218ac3cc7764070 Mon Sep 17 00:00:00 2001 From: donbr <7340008+donbr@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:41:33 -0700 Subject: [PATCH 3/4] docs(claude-md): document lazy routes and per-page titles Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ppa5DyJCZ1CowKtz58QARN --- CLAUDE.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1f17204..4783e00 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,7 +64,7 @@ Simple React Router setup in App.tsx: - `/assets/projects/advanced-retrieval` - AdvancedRetrieval - `*` - NotFoundPage (404) -No lazy loading is used - all components are directly imported. +HomePage and NotFoundPage are imported directly; every other route is loaded with `React.lazy` inside one `Suspense` boundary (fallback keeps the Layout visible). This keeps ECharts, Cytoscape and Leaflet out of the homepage bundle - add new project routes the same way. ### Project Data Management @@ -151,5 +151,6 @@ Located in `src/data/`: - No testing framework is currently configured - No formatter (like Prettier) is configured in package.json -- Project uses direct imports (no code splitting/lazy loading) +- Routes other than Home and 404 are code-split with `React.lazy` +- Each page renders its own `` inside `<Layout>` (React 19 hoists it into `<head>`); format: `Page Name | Don Branson` - Some project components include their own types (e.g., situational-awareness/types.ts) From 0a0ad035e97d46bf9449046f3b95bb7c534ee799 Mon Sep 17 00:00:00 2001 From: donbr <7340008+donbr@users.noreply.github.com> Date: Fri, 2 Oct 2026 14:08:07 -0700 Subject: [PATCH 4/4] chore: remove the Concepts page and its route The page was generated in b03de6c and opened with an anecdote that never happened. Removes ConceptsPage.tsx, its lazy route and the CLAUDE.md nav mention. /concepts now falls through to the 404 page. The nav link and sitemap entry are removed in #7, which should merge first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ppa5DyJCZ1CowKtz58QARN --- CLAUDE.md | 2 +- src/App.tsx | 2 - src/pages/ConceptsPage.tsx | 349 ------------------------------------- 3 files changed, 1 insertion(+), 352 deletions(-) delete mode 100644 src/pages/ConceptsPage.tsx diff --git a/CLAUDE.md b/CLAUDE.md index 4783e00..ae54bd2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,7 +79,7 @@ The ProjectCard component is also defined inline in ProjectsPage.tsx (lines 15-1 ### Layout Component The Layout component (`src/components/layout/Layout.tsx`) wraps all pages and provides: -- Navigation bar with links to sections (About, Expertise, Publications, Projects, Concepts, Teaching, Certifications, Contact, Graph Demos) +- Navigation bar with links to sections (About, Expertise, Publications, Projects, Teaching, Certifications, Contact, Graph Demos) - Active link highlighting using hash-based navigation for sections and pathname for Projects - Footer with social links (GitHub, LinkedIn, Graph Visualizations) diff --git a/src/App.tsx b/src/App.tsx index fcbc70f..9804632 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,6 @@ import NotFoundPage from '@/pages/NotFoundPage'; // Lazy-loaded routes: keeps ECharts, Cytoscape and Leaflet out of the homepage bundle const ProjectsPage = lazy(() => import('@/pages/ProjectsPage')); -const ConceptsPage = lazy(() => import('@/pages/ConceptsPage')); // Project Components const GdeltRecordViewer = lazy(() => import('@/components/projects/gdelt/GdeltRecordViewer')); @@ -34,7 +33,6 @@ const App: React.FC = () => { <Routes> <Route path="/" element={<HomePage />} /> <Route path="/assets/projects" element={<ProjectsPage />} /> - <Route path="/concepts" element={<ConceptsPage />} /> {/* Project routes */} <Route path="/assets/projects/gdelt" element={<GdeltRecordViewer />} /> diff --git a/src/pages/ConceptsPage.tsx b/src/pages/ConceptsPage.tsx deleted file mode 100644 index 96a1473..0000000 --- a/src/pages/ConceptsPage.tsx +++ /dev/null @@ -1,349 +0,0 @@ -import React from 'react'; -import Layout from '@/components/layout/Layout'; - -const ConceptsPage: React.FC = () => { - return ( - <Layout> - <title>MCP Architecture: Concepts Before Code | Don Branson - {/* Hero Section */} -
-
-

- MCP Architecture: Concepts Before Code -

-

- Frame the problem first. Understand the trade-offs. Then write the code. -

-
-
- - {/* Introduction */} -
-
-
-

- A student once asked me: "Should I refactor my FastAPI RAG system to use FastMCP?" -

-

- My answer? "It depends. Let's talk about what problem you're actually solving." -

-
-

- This page isn't about code syntax. It's about architectural decisions β€” - when to use MCP tools vs resources, when FastAPI still wins, and how CQRS patterns help you think clearly about these choices. -

-
-
- - {/* MCP Decision Framework */} -
-
-

- The MCP Decision Framework -

- -
- {/* MCP Tools */} -
-

MCP Tools πŸ”§

-

Use when you need to change state

- -
-

Good For:

-
    -
  • β€’ Running searches (query changes each time)
  • -
  • β€’ Creating/updating records
  • -
  • β€’ Executing actions with side effects
  • -
  • β€’ Real-time operations that need parameters
  • -
-
- -
-

Ask yourself:

-

"Does this operation need unique input each time it runs?"

-

If yes β†’ Tool

-
-
- - {/* MCP Resources */} -
-

MCP Resources πŸ“š

-

Use when you need to read state

- -
-

Good For:

-
    -
  • β€’ Static or semi-static data
  • -
  • β€’ Configuration files
  • -
  • β€’ Documentation that doesn't change often
  • -
  • β€’ Cacheable content
  • -
-
- -
-

Ask yourself:

-

"Can this data be cached and reused?"

-

If yes β†’ Resource

-
-
-
- - {/* CQRS Connection */} -
-

The CQRS Connection

-

- If you're familiar with CQRS (Command Query Responsibility Segregation), this framework should feel natural: -

-
-
-

Commands (write)

-

Change state, have side effects

-

β†’ MCP Tools

-
-
-

Queries (read)

-

Read state, no side effects

-

β†’ MCP Resources

-
-
-
-
-
- - {/* FastAPI vs FastMCP */} -
-
-

- FastAPI β†’ FastMCP: The Migration Pattern -

- -
-

- The question isn't "Should I migrate?" -

-

- The question is: "What integration point matters most to me?" -

-
- -
- {/* FastAPI Strengths */} -
-

FastAPI Strengths πŸš€

-
    -
  • - βœ“ - Simple setup β€” pip install, run server, done -
  • -
  • - βœ“ - HTTP everywhere β€” works with any client -
  • -
  • - βœ“ - Mature ecosystem β€” tons of middleware, auth, etc. -
  • -
  • - βœ“ - Flexibility β€” full control over implementation -
  • -
- -
-

Choose FastAPI when:

-

- You need a general-purpose API that non-AI clients will also use, or when you need full control over auth, rate limiting, and middleware. -

-
-
- - {/* FastMCP Strengths */} -
-

FastMCP Strengths ⚑

-
    -
  • - βœ“ - Native IDE integration β€” Claude Desktop, Cursor, Zed -
  • -
  • - βœ“ - Resource caching β€” built into the protocol -
  • -
  • - βœ“ - Standard discovery β€” servers are auto-discoverable -
  • -
  • - βœ“ - Context-aware β€” designed for AI workflows -
  • -
- -
-

Choose FastMCP when:

-

- Your primary users are AI applications/IDEs, you want standardized tool/resource patterns, or you're building for the MCP ecosystem. -

-
-
-
- - {/* The Hybrid Approach */} -
-

πŸ’‘ The Hybrid Approach

-

- You don't have to choose! Many production systems run both: -

-
    -
  • β€’ FastAPI for your main application and web clients
  • -
  • β€’ FastMCP server for IDE/AI integration
  • -
  • β€’ Both calling the same business logic layer
  • -
-

- This is often the right answer for production systems that need broad compatibility. -

-
-
-
- - {/* Real-World Scenarios */} -
-
-

- Real-World Decision Scenarios -

- -
- {/* Scenario 1 */} -
-

- Scenario 1: RAG System with Vector Search -

-

- "I have a RAG system where users search documentation. Should the search endpoint be FastAPI or FastMCP?" -

-
-

Recommendation: FastMCP Tool

-

- Why: Each search query is unique (dynamic input), and if you're using Claude/IDEs, - MCP gives native integration. The search results aren't cacheable since the query changes. -

-

- But consider FastAPI if: You need the API for a web frontend too, or you have complex authentication needs. -

-
-
- - {/* Scenario 2 */} -
-

- Scenario 2: LangSmith Trace Analysis -

-

- "I want to query my LangSmith traces from Claude Desktop. Tool or Resource?" -

-
-

Recommendation: MCP Resource

-

- Why: Traces are historical data (read-only), they don't change frequently, - and caching can significantly improve performance. Resources are perfect for this. -

-

- Use a Tool for: Creating new traces, updating metadata, or running analytics that need parameters. -

-
-
- - {/* Scenario 3 */} -
-

- Scenario 3: Multi-Agent Orchestration -

-

- "I'm building a multi-agent system with LangGraph. How do I expose agent capabilities?" -

-
-

Recommendation: Hybrid Approach

-

- MCP Tools for invoking agents (dynamic, stateful operations) -

-

- MCP Resources for agent configurations, schemas, available tools -

-

- Consider FastAPI if you need a REST API for monitoring dashboards or external integrations -

-
-
-
-
-
- - {/* Code Examples Link */} -
-
-

- Ready to See the Code? -

-

- Now that you understand the concepts and trade-offs, check out working implementations: -

- -
-
- - {/* Questions Section */} -
-
-

- Still Have Questions? -

-
-

- These architectural decisions depend heavily on your specific use case. If you're a student in one of my bootcamp cohorts, - bring your scenario to office hours. If not, feel free to reach out: -

- -
-
-
- - ); -}; - -export default ConceptsPage;