Next.js frontend for RustAcademy — Learn Rust, earn XLM, build Web3.
RustAcademy Web provides the learner, tutor, and community experience for the platform.
Users can:
- Browse Rust courses
- Complete coding challenges
- Chat with the AI Mentor
- Join community discussions
- Manage rewards and certifications
- Connect Stellar wallets
- Track XP, streaks, and achievements
- Install as an app (PWA) and keep working offline
- Course catalog
- Lesson player
- Interactive coding environment
- Progress tracking
- Quiz system
- Task submissions
- Rust tutoring
- Soroban assistance
- Code explanations
- Error debugging
- Personalized recommendations
- Course creation
- Task management
- Submission reviews
- Earnings dashboard
- Social feed
- Comments & reactions
- Study groups
- Direct messaging
- Freighter integration
- Reward tracking
- Certificate viewing
- Badge collection
- Username-based payment pages (
/[username]) - Real-time profile fetching from backend API
- Error handling with fallback UI
- Server-side validation and metadata generation
- localStorage fallback for additional profile metadata
- 404 error pages with navigation
- Next.js 15
- TypeScript
- Tailwind CSS v4
- shadcn/ui
- Zustand
- TanStack Query
- Framer Motion
- Monaco Editor
- Socket.io Client
- Stellar SDK
src/
├── app/
├── components/
├── hooks/
├── lib/
├── providers/
├── store/
├── types/
└── styles/NEXT_PUBLIC_API_URL=http://localhost:4000
NEXT_PUBLIC_WS_URL=ws://localhost:4000
# RustAcademy Backend API URL (overrides NEXT_PUBLIC_API_URL for profile endpoints)
NEXT_PUBLIC_RustAcademy_API_URL=http://localhost:4000
NEXT_PUBLIC_STELLAR_NETWORK=testnet
NEXT_PUBLIC_REWARD_POOL_CONTRACT_ID=
NEXT_PUBLIC_CERTIFICATE_CONTRACT_ID=
NEXT_PUBLIC_BADGE_CONTRACT_ID=
NEXT_PUBLIC_REPUTATION_CONTRACT_ID=pnpm install
pnpm devRuns on:
http://localhost:3000pnpm build
pnpm startpnpm testRegister
↓
Connect Wallet
↓
Enroll in Course
↓
Complete Tasks
↓
AI/Tutor Review
↓
Earn XLM
↓
Receive NFT Certificate
RustAcademy Web is an installable Progressive Web App with offline-first caching and safe refresh timing.
| Component | File | Role |
|---|---|---|
| Web Manifest | src/app/manifest.ts |
App metadata: name, icons, theme, standalone display, and shortcuts (Generate Link, Dashboard). Served at /manifest.webmanifest. |
| Service Worker | public/sw.js |
Precaches app shell and offline page. Implements network-first for navigations and cache-first for assets. Handles stale-while-revalidate for better offline UX. |
| Install/Update Handler | src/components/PWAHandler.tsx |
Registers service worker, manages install/update prompts with safe timing and dismissal logic. |
| Online Status Provider | src/lib/onlineStatus.tsx |
Real-time online/offline state management. Available globally via useOnlineStatus() hook. |
| Offline Indicator | src/components/OnlineStatusBadge.tsx |
Shows user when they're offline and how long they've been offline. |
| Offline Page | src/app/offline/page.tsx |
User-friendly fallback when navigations fail and no cache exists. Auto-redirects when connection restored. |
| Error Reporting | src/lib/errorReporter.ts + src/components/ErrorReportingShell.tsx |
Captures errors across the app with full context and PII redaction. |
┌─ User visits app (first time)
│
├─ Service worker registers
│ └─ Precaches app shell + offline page + icons
│
├─ 3 seconds after page load
│ └─ beforeinstallprompt fires
│
├─ PWAHandler shows install banner (if eligible)
│ • Not already installed (standalone mode)
│ • Not dismissed in last 7 days
│ • User is online
│
├─ User clicks "Install Now"
│ └─ Native install prompt (browser/platform-specific)
│
└─ appinstalled event fires
└─ Banner hidden, app marked as installed
Safety Features:
- Install prompt only shows when online (better UX, no network errors during install)
- 3-second delay prevents prompt distraction on initial page load
- Dismissing prompt doesn't break navigation or app functionality
- Automatic re-offer after 7 days if user dismisses
- iOS "Add to Home Screen" flow handled seamlessly
┌─ New version deployed
│
├─ Service worker detects update
│ └─ updatefound event fires
│
├─ New worker installs and reaches "waiting" state
│ └─ PWAHandler detects new version available
│
├─ PWAHandler shows update banner (if online)
│ • Allows user to refresh now or later
│ • Dismissing doesn't break current session
│
├─ User clicks "Refresh Now"
│ └─ Window reloads, new SW activated
│
└─ New version ready to use
Safety Features:
- Updates detected automatically via periodic checks (every 60 seconds)
- Update banner only shows when online
- User can dismiss update and continue using current version
- Refresh is non-destructive (just reloads page)
- Service worker periodically checks for updates even without banner
- Strategy: Network-first with offline fallback
- Behavior:
- Try to fetch fresh page from network
- If network succeeds → cache response and return it
- If network fails → return cached copy (if available)
- If no cache exists → return
/offlinepage
- Use Case: Always show latest content when online; gracefully degrade offline
- Example: User navigates to
/dashboard— gets latest data if connected, or last cached version if offline
- Strategy: Cache-first with stale-while-revalidate
- Behavior:
- Check cache for asset
- If fresh (within 24 hours) → return immediately
- If stale (older than 24 hours) → fetch fresh copy in background
- If fetch fails → return stale cache (if available)
- If no cache exists → return 408 offline response
- Rationale: Hashed Next.js assets (
/_next/static/) are immutable, so cache hits are always correct - Example: CSS, JS bundles, images served from cache instantly, with optional refresh in background
- API Routes:
/api/*— always fetched live - Cross-Origin: Requests to external domains
- Rationale: Payment data and real-time information must be live
Cache Versioning:
- Service worker versioned by
VERSIONconstant inpublic/sw.js - Update
VERSIONwhen changing cache behavior - Old caches automatically deleted on service worker activation
import { useOnlineStatus } from "@/lib/onlineStatus";
export function MyComponent() {
const { isOnline, wasOffline, offlineSince } = useOnlineStatus();
return <div>Status: {isOnline ? "Online" : "Offline"}</div>;
}- Initial:
isOnline = navigator.onLine(reflects network status) - Goes Offline:
isOnline = false,wasOffline = true,offlineSince = Date - Goes Online:
isOnline = true,offlineSince = undefined - SessionStorage:
wasOfflinepersists for session duration (cleared on new session)
- OnlineStatusBadge: Shows offline duration (mobile only by default)
- PWAHandler: Disables install/update prompts when offline
- Offline Page: Shows friendly message with retry button
- Prompts appear only in eligible contexts (not installed, not dismissed, online)
- Dismissing without breaking navigation (non-destructive)
- Install banner shown 3s after page load (not intrusive)
- Update banner shown when new version detected
- Both prompts can be dismissed; user can continue using app
- Gracefully shown when cached routes unavailable
- Clear messaging about offline state
- Retry button for connection recovery
- Auto-redirect when connection restored
- "Go Home" button for manual navigation
- Documented in this README (you're reading it!)
- Aligned with deployment behavior:
- VERSION bump = auto cache cleanup
- Hashed assets = immutable cache hits
- Network-first navigations = always fresh when possible
- Stale-while-revalidate for better offline UX
- PII redaction in error reporting
Errors that occur while offline are automatically queued using IndexedDB and retried when connectivity is restored.
- Error Queueing: Errors stored in IndexedDB when offline
- Automatic Retry: Queued errors auto-resend when online
- Retry Limits: Max 3 retry attempts per error
- Graceful Cleanup: Failed errors removed after max retries
No manual setup needed — errors are automatically queued:
// This error will be queued if offline, then retried when online
throw new Error("Something went wrong");
// Unhandled promise rejections are also queued
Promise.reject("Network failed");- Stored in IndexedDB database
rustacademy-error-queue - Persists across page reloads
- Cleared automatically after successful transmission
- Older errors removed first if queue fills up
Real-time connectivity detection beyond navigator.onLine:
const { isOnline, isCheckingConnectivity } = useOnlineStatus();
// isOnline updates based on:
// 1. navigator.onLine (offline mode detection)
// 2. Periodic connectivity heartbeats (every 30s)
// 3. Real network requests (not just absence of offline mode)The heartbeat uses a lightweight HEAD request to /manifest.webmanifest with a 5-second timeout. If it times out or fails, the app detects degraded connectivity even when navigator.onLine says true.
Benefits:
- Detects network unavailability beyond offline mode
- Prevents showing "online" with 0% connectivity
- Updates app state reactively when connectivity changes
Service workers require HTTPS (or localhost). To test:
cd app/frontend
# Build and start production server
pnpm build
pnpm startThen in Chrome DevTools:
- Application → Manifest: Verify installability and metadata
- Application → Service Workers: Confirm
sw.jsis activated - Application → Cache Storage: View cached pages and assets
- Network → Offline: Reload to test offline behavior
- DevTools → Sensors → Network: Simulate online/offline transitions
Testing on Real Devices:
- Android: Use Chrome DevTools remote debugging
- iOS: Settings → Safari → Advanced → Web Inspector (requires macOS with Safari)
- Device Offline: Toggle airplane mode, disconnect WiFi, or use browser's throttling
Before deploying PWA changes:
- Service Worker Versioning: Bump
VERSIONinpublic/sw.jsif cache strategy changed - Icons: Ensure
icon-192.pngandicon-512.pngexist and are optimized - Manifest: Verify
start_url,scope, and metadata are correct - HTTPS: Ensure app is served over HTTPS (PWA requirement)
- Test Offline: Verify offline page loads when network unavailable
- Test Update: Deploy dummy version change, verify update prompt works
- Error Reporting: Verify error reporting endpoint is configured and receiving errors
- Mobile Testing: Test install flow on iOS and Android devices
- Cache Cleanup: Old versions should be cleaned up after new deployment
# Error Reporting (optional)
NEXT_PUBLIC_ERROR_REPORTING_ENABLED=true
NEXT_PUBLIC_ERROR_REPORTING_URL=https://your-error-service.com/errors- First Install: ~2MB (app shell + icons)
- Cache Cleanup: Automatic on SW activation
- Update Check: Every 60 seconds (efficient polling)
- Install Prompt Delay: 3 seconds (prevents distraction)
Install Banner Not Showing
- Not on HTTPS/localhost? PWA requires secure context
- Already installed? Uninstall from system settings
- Recently dismissed? Wait 7 days or clear localStorage (
pwa-install-dismissed-at) - Offline? Banner only shows online
Update Not Working
- Service worker running? Check DevTools → Application → Service Workers
- Check DevTools → Network to see if new SW is fetching
- Browser cache interfering? Hard refresh (
Ctrl+Shift+R) - Try manual check: DevTools → Application → Service Workers → Update
Offline Page Not Showing
- Network offline but route cached? Network-first should serve cache
- No cache and offline?
/offlinepage should load - Check DevTools → Cache Storage for precached assets
High Cache Usage
- Check DevTools → Application → Storage
- Delete old caches manually (bump VERSION to auto-cleanup)
- Implement cache size limits if needed (not built-in)
Recommended:
- Frontend → Vercel
- Assets → Cloudflare R2