As a developer, I've always been frustrated by the mortgage
calculators available online. Most assume you have a single mortgage with a
fixed rate for the entire term, but real-world mortgages are far more complex.
That's why I decided to build a comprehensive mortgage repayment calculator that
actually reflects how modern mortgages work.
In this first blogpost I'll write about the some of the technical considerations of building LoanPlannerPro.com. In future posts I'll discuss more technical challenges, internationalisation (i18n), deployment, hosting, design, revenue, promotion and much more.
Traditional mortgage calculators fall short in several key areas:
Single mortgage assumption
Most calculators only handle one mortgage at a time.
Oversimplified interest rates
They don't account for different risk classes based on Loan-to-Value (LTV) ratios.
No fixed period modeling
They can't handle scenarios where you have a fixed rate for a certain number of years that then reverts to a variable rate.
Poor mobile experience
Many are desktop-centric with clunky mobile interfaces.
Static calculations
You often need to hit "calculate" buttons instead of seeing real-time updates
I chose React.js with Vite for the frontend because Vite provides lightning-fast hot module replacement during development and React's component-based architecture perfectly suits the modular nature of the mortgage parts.
For styling I chose TailwindCSS because it enables rapid prototyping with utility classes and it has a consistent design system and that is easily maintainable. Dark mode support is built-in and trivial to implement. Same goes for the Mobile-first approach I wanted to employ, this aligns perfectly with my responsive design goals. Also, I didn't want to spend a lot of time on writing CSS rules but focus the little time I have on the functionality of the calculator instead.
Rather than reaching for Redux or Zustand, I implemented a functional programming approach using React's built-in hooks with localStorage persistence. Here is some code from the project to see what that looks like:
// State for mortgage parts - load from localStorage if available
const [mortgageParts, setMortgageParts] = useState(() => {
const savedMortgageParts = localStorage.getItem('mortgageParts');
return savedMortgageParts ? JSON.parse(savedMortgageParts) :[DEFAULT_MORTGAGE_PART];
});
// Save mortgage parts to localStorage whenever they change
useEffect(() => {
localStorage.setItem('mortgageParts', JSON.stringify(mortgageParts));
}, [mortgageParts]);
This approach ensures Predictable state updates by using pure functions. Pure functions also make debugging a lot easier. Data persistence by using LocalStorage, this way the user inputs survive browser refreshes and sessions. Real-time reactivity, useEffect hooks trigger recalculations on any input change. Testability is also improved this way because pure calculation functions are easy to unit test. The solution is Client-side only, keeps things simple. No server is required, this way I can keep hosting costs to a minimum.
The core challenge was building a calculation engine that handles complex mortgage structures while remaining performant. Each mortgage part can have:
Individual interest rates with multiple risk classes
Different mortgage types (annuity, linear, interest-only)
Fixed-rate periods that transition to variable rates
Additional payments with custom timing and duration
Overlapping time periods across multiple parts
The solution uses a month-by-month calculation approach, here is more code from the app:
// Calculate mortgage payments
const calculateMortgage = useCallback(() => {
const results = calculateAmortizationSchedule(mortgageParts, propertyValue);
setCalculationResults(results);
}, [mortgageParts, propertyValue]);
// Calculate whenever mortgageParts change
useEffect(() => {
calculateMortgage();
}, [mortgageParts, calculateMortgage]);
Thank you for reading my post! The mortgage calculator is currently in active development, please have a look at it while I'm developing. Looking forward to seeing your feedback.