vite-plugin-smart-prefetch
v0.3.3
Published
Smart prefetch plugin for Vite with BigQuery GA4 analytics. Supports React Router DOM and TanStack Router with intelligent dynamic route matching.
Maintainers
Readme
@farmart/vite-plugin-smart-prefetch
Smart prefetch plugin for Vite applications powered by BigQuery GA4 analytics
Automatically prefetch route chunks based on real user navigation patterns from BigQuery GA4 export. Improve perceived performance by up to 80-90% for predicted route transitions.
Features
- 🤖 Smart Learning - Analyzes BigQuery GA4 event data to learn user navigation patterns
- 📊 Data-Driven - Prefetch decisions based on actual user behavior, not assumptions
- ⚡ Multiple Strategies - Auto, hover, visible, idle, or hybrid prefetching
- 🌐 Network-Aware - Respects data-saver mode and connection quality
- 💾 Smart Caching - Caches computed models with TTL for efficiency
- 🎯 Framework Support - React (Vue and Svelte coming soon)
- 🔧 Zero Config - Works out of the box with sensible defaults
- 📈 Environment-Specific - Different models for production, staging, development
Installation
# Using pnpm (recommended for monorepos)
pnpm add @farmart/vite-plugin-smart-prefetch
# Using npm
npm install @farmart/vite-plugin-smart-prefetch
# Using yarn
yarn add @farmart/vite-plugin-smart-prefetchQuick Start
1. Setup BigQuery for GA4 Export
# 1. Enable BigQuery export from GA4 Console
# GA4 Admin > Data Export > BigQuery
# 2. Create Google Cloud service account
# https://console.cloud.google.com/iam-admin/serviceaccounts
# 3. Grant "BigQuery Data Viewer" and "BigQuery Job User" roles
# 4. Create and download JSON key
# Service Accounts > Keys > Add Key > Create New Key > JSON
# 5. Add to environment variables.env:
VITE_GA_PROPERTY_ID=123456789
VITE_GA_CLIENT_EMAIL=prefetch@your-project.iam.gserviceaccount.com
VITE_GA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"2. Configure Vite Plugin
vite.config.mts:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { smartPrefetch } from '@farmart/vite-plugin-smart-prefetch';
export default defineConfig({
plugins: [
react(),
smartPrefetch({
framework: 'react',
strategy: 'hybrid',
analytics: {
provider: 'google-analytics',
credentials: {
propertyId: process.env.VITE_GA_PROPERTY_ID!,
clientEmail: process.env.VITE_GA_CLIENT_EMAIL!,
privateKey: process.env.VITE_GA_PRIVATE_KEY!,
},
dataRange: {
days: 30, // Analyze last 30 days
minSessions: 100, // Minimum sessions threshold
},
model: {
type: 'probability',
threshold: 0.3, // 30% probability threshold
maxPrefetch: 3, // Max 3 routes per page
},
environment: process.env.NODE_ENV || 'production',
},
manualRules: {
// Optional: Override ML predictions
'/checkout': ['/payment'],
},
cache: {
enabled: true,
ttl: 24 * 60 * 60 * 1000, // 24 hours
},
}),
],
});3. Add to Your App
App.tsx:
import { usePrefetch } from '@farmart/vite-plugin-smart-prefetch/react';
function App() {
// That's it! Plugin handles everything
usePrefetch();
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/orders" element={<Orders />} />
<Route path="/dispatch-order" element={<DispatchOrder />} />
</Routes>
);
}4. Build & Deploy
npm run buildThe plugin will:
- ✅ Connect to Google Analytics
- ✅ Fetch navigation data
- ✅ Train probability model
- ✅ Generate
prefetch-config.json - ✅ Cache model for future builds
Configuration
Plugin Options
interface PluginOptions {
/** Framework (default: 'react') */
framework?: 'react' | 'vue' | 'svelte';
/** Prefetch strategy (default: 'hybrid') */
strategy?: 'auto' | 'hover' | 'visible' | 'idle' | 'hybrid';
/** Google Analytics integration */
analytics?: AnalyticsConfig;
/** Manual prefetch rules */
manualRules?: {
[sourceRoute: string]: string[];
};
/** Cache configuration */
cache?: {
enabled?: boolean;
ttl?: number;
path?: string;
};
/** Advanced options */
advanced?: {
debug?: boolean;
excludeRoutes?: string[];
};
}Prefetch Strategies
| Strategy | Description | Use Case |
|----------|-------------|----------|
| auto | Prefetch immediately on route change | High-confidence predictions |
| hover | Prefetch when user hovers over link | User intent signals |
| visible | Prefetch when link becomes visible | Below-the-fold content |
| idle | Prefetch during browser idle time | Low priority routes |
| hybrid | Smart combination based on probability | Recommended |
Hybrid Strategy (Recommended)
- High priority (>70% probability): Prefetch immediately
- Medium priority (40-70%): Prefetch on idle
- Low priority (30-40%): Prefetch on hover/visible
React Integration
usePrefetch Hook
import { usePrefetch } from '@farmart/vite-plugin-smart-prefetch/react';
function App() {
// Automatically prefetches routes based on navigation
usePrefetch();
return <Routes>...</Routes>;
}PrefetchLink Component
Enhanced Link component with manual control:
import { PrefetchLink } from '@farmart/vite-plugin-smart-prefetch/react';
// Prefetch on hover
<PrefetchLink to="/orders" prefetch="hover">
View Orders
</PrefetchLink>
// Prefetch when visible (great for lists)
<PrefetchLink to="/dispatch-order" prefetch="visible">
Create Dispatch
</PrefetchLink>
// Manual control (no automatic prefetch)
<PrefetchLink to="/settings" prefetch="manual">
Settings
</PrefetchLink>Manual Prefetch Control
import { getPrefetchManager } from '@farmart/vite-plugin-smart-prefetch/react';
function MyComponent() {
const handleMouseEnter = () => {
const manager = getPrefetchManager();
manager?.prefetchRoute('/orders');
};
return (
<button onMouseEnter={handleMouseEnter}>
View Orders
</button>
);
}How It Works
Build Time
1. Connect to Google Analytics
├── Authenticate with service account
└── Fetch last 30 days of navigation data
2. Train Model
├── Calculate transition probabilities
│ Example: P(/dispatch-order | /orders) = 0.58
├── Apply threshold filter (>30%)
└── Select top 3 predictions per route
3. Generate Configuration
├── Map routes to Vite chunks
│ /dispatch-order → assets/dispatch-order-abc123.js
└── Emit prefetch-config.json
4. Cache Model
├── Save to node_modules/.cache/smart-prefetch/
└── TTL: 24 hours (configurable)Runtime
1. App Loads
├── usePrefetch() initializes
└── Fetch /prefetch-config.json
2. User on /orders page
├── Check network conditions (Data Saver, 2G, etc.)
├── Read predictions: [/dispatch-order (58%), /orders/:id (42%)]
└── Apply strategy:
- High priority: Prefetch immediately
- Medium: Prefetch on idle
- Low: Wait for hover/visible
3. Inject <link rel="prefetch">
<link rel="prefetch" href="/assets/dispatch-order-abc123.js">
4. User clicks "Dispatch Order"
├── Chunk already in browser cache!
└── Load time: ~10ms instead of ~300msPerformance Impact
Before vs After
| Metric | Without Prefetch | With Prefetch | Improvement | |--------|------------------|---------------|-------------| | Route transition | 250-500ms | 10-50ms | 80-90% faster | | Cache hit rate | 0% | 60-80% | New capability | | Time to Interactive | Baseline | -15-20% | Faster |
Real-World Example
Example: /orders → /dispatch-order (58% probability)
Without prefetch:
1. User clicks link
2. Download dispatch-order-abc123.js (300ms)
3. Parse and execute (50ms)
Total: 350ms
With prefetch:
1. User lands on /orders
2. Plugin prefetches dispatch-order-abc123.js in background
3. User clicks link
4. Load from cache (5ms)
5. Parse and execute (50ms)
Total: 55ms (84% faster!)Network-Aware Prefetching
The plugin automatically detects and respects:
- ✅ Data Saver mode - No prefetch if enabled
- ✅ Slow connections - No prefetch on 2G/slow-2G
- ✅ Metered connections - Conservative prefetch on cellular
- ✅ Connection quality - Uses Network Information API
// Automatic detection (no configuration needed)
if (navigator.connection.saveData) {
// Skip prefetch
}
if (connection.effectiveType === '2g') {
// Skip prefetch
}Cache Management
Cache Location
node_modules/.cache/smart-prefetch/
├── model-production.json # Production model
├── model-staging.json # Staging model
└── metadata.json # Cache metadataInvalidate Cache
# Invalidate all cache
rm -rf node_modules/.cache/smart-prefetch
# Or programmatically
const cacheManager = new CacheManager();
await cacheManager.invalidate();Cache Stats
const stats = cacheManager.getStats();
// {
// enabled: true,
// cacheDir: './node_modules/.cache/smart-prefetch',
// ttl: 86400000,
// cachedEnvironments: ['production', 'staging'],
// totalSize: 45678
// }Manual Rules
Override ML predictions with manual rules:
smartPrefetch({
manualRules: {
// Always prefetch payment on checkout
'/checkout': ['/payment'],
// Multiple targets
'/': ['/products', '/orders', '/dashboard'],
// Dynamic routes (use :id notation)
'/orders/:id': ['/dispatch-order/:id'],
},
})Manual rules:
- ✅ Take precedence over ML predictions
- ✅ Always have
priority: 'high' - ✅ Always have
probability: 1.0 - ✅ Merged with analytics data
Debugging
Enable debug mode to see detailed logs:
smartPrefetch({
advanced: {
debug: true,
},
})Build-Time Logs
🚀 Smart Prefetch Plugin Initialized
Framework: react
Strategy: hybrid
Analytics: enabled
📊 Fetching analytics data...
Date range: Last 30 days
Min sessions threshold: 100
✅ Fetched 87 navigation patterns
🤖 Training prefetch model...
Model type: probability
Threshold: 30%
Max prefetch per route: 3
✅ Model trained successfully
Routes with predictions: 42
📦 Generating prefetch configuration...
Manifest entries: 156
Mapped chunks: 89Runtime Logs
🚀 Prefetch Manager Initialized
Strategy: hybrid
Routes: 42
Environment: production
📦 Prefetching for route: /orders
Targets: 2
✅ Prefetched: /dispatch-order (58%, high)
✅ Prefetched: /orders/:id (42%, medium)Troubleshooting
Issue: "Failed to load prefetch config"
Solution: Ensure /prefetch-config.json is in your build output (dist/).
# Check if file exists
ls dist/prefetch-config.jsonIssue: "Google Analytics connection test failed"
Solutions:
- Verify service account has "Viewer" access to GA4 property
- Check credentials in
.envfile - Ensure private key has correct newlines (
\n)
# Test credentials
echo $VITE_GA_PRIVATE_KEY | grep "BEGIN PRIVATE KEY"Issue: "No navigation data found"
Solutions:
- Ensure GA4 property has at least 30 days of data
- Lower
minSessionsthreshold - Check GA4 property ID is correct
Issue: "No chunk found for route"
Solution: Adjust route normalization or use manual rules:
smartPrefetch({
manualRules: {
'/my-route': ['/target-route'],
},
advanced: {
routeNormalization: (path) => {
// Custom normalization logic
return path.replace(/\/[0-9]+/g, '/:id');
},
},
})Best Practices
1. Use Hybrid Strategy
strategy: 'hybrid' // ✅ RecommendedAutomatically prioritizes prefetch based on prediction confidence.
2. Set Appropriate Thresholds
model: {
threshold: 0.3, // 30% - Good balance
maxPrefetch: 3, // Limit bandwidth usage
}- Too low (<20%): Wastes bandwidth
- Too high (>50%): Misses opportunities
3. Use Manual Rules for Critical Paths
manualRules: {
'/checkout': ['/payment'], // Always prefetch payment
}4. Monitor Prefetch Performance
Enable dashboard to visualize predictions:
analytics: {
dashboard: true, // Generates prefetch-report.html
}Access at: https://your-app.com/prefetch-report.html
5. Environment-Specific Models
// Production
environment: 'production'
// Staging
environment: 'staging'Keeps models separate for different user behaviors.
API Reference
See RFC-001-SMART-PREFETCH-PLUGIN.md for detailed API documentation.
Contributing
This is an internal FarmArt Engineering package. For issues or feature requests, please contact the engineering team.
License
MIT © FarmArt Engineering
Support
For questions or support:
- Internal Slack:
#engineering - Documentation: See RFC-001 in project root
- Issues: Create ticket in project management tool
