Building a modern web application is easy. Building one that scores a perfect 100/100 on Google Lighthouse across Performance, Accessibility, Best Practices, and SEO? That requires obsessing over the details.
Recently, I launched
Order Block Trading
, an educational platform designed to teach retail traders how to navigate the markets using institutional concepts (Smart Money Concepts). But this post isn't about financeβit's about the technical journey, the bottlenecks I hit, and the architectural decisions that made this platform blazing fast.
Hereβs a deep dive into how I built it, the performance traps I fell into, and how I fixed them.
π οΈ The Tech Stack
I wanted a stack that was lightweight, fast, and easy to deploy without massive cloud overhead.
Frontend: React (bootstrapped with Vite)
Backend: Node.js with Express
Database: SQLite (using better-sqlite3)
Styling: Vanilla CSS (No bloated UI frameworks)
Why SQLite? Because for a content-heavy application where read operations vastly outnumber write operations, SQLite is incredibly fast. Using better-sqlite3 allowed me to write raw, synchronous SQL queries without the overhead of heavy ORMs.
ποΈ The Quest for 100/100 Performance
The trading education space is notorious for slow, ad-heavy WordPress blogs. I wanted Order Block Trading Academy to feel frictionless. However, getting there required solving several major performance bottlenecks.
- The React.lazy() Code Splitting Epiphany Initially, my entire React application was bundled into a single massive chunk. The public-facing site was fast enough, but I had built a robust Admin panel that included heavy dependencies like react-md-editor (for writing articles) and pdfjs-dist (for reading resources).
Because they were in the main bundle, a user visiting the homepage had to download megabytes of Markdown parser code just to view the hero section!
The Fix: I wrapped all my routes in React.lazy and Suspense.
javascript
import { lazy, Suspense } from 'react';
const Home = lazy(() => import('./pages/Home'));
const ManageArticles = lazy(() => import('./pages/admin/ManageArticles'));
// ... inside the router
}>
} />
} />
This instantly isolated the heavy Admin dependencies into separate chunks. The initial payload dropped drastically, and the homepage Time to Interactive (TTI) became virtually instantaneous.
- The Promise.all Trap (A Lesson in FCP) In my global DataContext.jsx, I needed to fetch categories, articles, and forum threads on mount. Being a "smart" developer, I decided to fetch them in parallel to save time:
javascript
// The Bad Approach
const [resCats, resArts, resForums] = await Promise.all([
fetch('/api/categories'),
fetch('/api/articles'),
fetch('/api/forums') // This endpoint was heavy!
]);
setIsInitialized(true); // Unblocks the UI
This looked great on my local machine. But when I throttled the network to "Slow 4G" to simulate mobile users, my First Contentful Paint (FCP) spiked to 4.9 seconds!
By using Promise.all and blocking the UI (isInitialized), I forced the app to wait for the slowest API call (the heavy forums) before rendering anything. The Homepage didn't even need the forums data!
The Fix: I split the data fetching into Critical and Non-Critical blocks.
javascript
// 1. Fetch critical data needed for first paint
await Promise.all([fetch('/api/categories'), fetch('/api/articles')]);
setIsInitialized(true); // UNBLOCK THE UI IMMEDIATELY!
// 2. Fetch non-critical data in the background
fetch('/api/forums').then(...);
By letting the critical data unblock the UI, the FCP dropped to under 1 second on mobile.
- Image Optimization & Layout Shifts (CLS) Images are always the silent killers of performance. I had several raw, unoptimized JPEGs in my src/assets folder totaling over 3MB. To fix this, I created a strict workflow:
Offloading: I wrote a Node script to interact with the ImgBB API, moving all heavy assets off my local server and onto a CDN.
Preventing CLS: I ensured every single tag had explicit width and height attributes to reserve space in the DOM before the image loaded, eliminating Cumulative Layout Shift (CLS).
π§ Programmatic SEO: Automating Internal Linking
As the platform grew, I realized manually adding internal links to my 2000+ word Markdown articles was unsustainable. Internal linking is crucial for SEO, so I wrote a custom Node.js script to automate it.
The challenge: How do you replace keywords (like "Order Block" or "Fair Value Gap") with markdown links, without accidentally corrupting existing links or image tags?
I avoided complex regex negative lookaheads and used a simpler approach: splitting the string by Markdown links first.
javascript
// Extract all text, split by markdown links/images
const parts = articleContent.split(/(!?[.?](.?))/g);
for (let i = 0; i < parts.length; i++) {
// Only apply keyword replacement to plain text (even indices)
if (i % 2 === 0) {
parts[i] = parts[i].replace(/\b(Order Blocks?)\b/gi, 'Order Block');
}
}
const newContent = parts.join('');
This script ran through my entire SQLite database in milliseconds, flawlessly hyperlinking every technical term to its respective guide.
π― Conclusion
Building Order Block Trading Academy reminded me that performance is a feature. You don't always need Next.js or a massive cloud architecture to build a fast application. A raw React SPA, powered by Vite and a clean Express/SQLite backend, can achieve perfect Lighthouse scores if you respect the fundamentals:
Don't block the critical rendering path.
Code-split aggressively.
Automate your SEO and image optimizations.
If youβre interested in checking out the speed (or learning a bit about institutional trading), check out the live platform here: [Link to Project]
I'd love to hear your thoughts on these optimization strategies in the comments! π
Top comments (0)