Compare commits
26 Commits
main
...
541c443efd
Author | SHA1 | Date | |
---|---|---|---|
541c443efd | |||
0d12ab9a47 | |||
09e9485f2f | |||
383625aede | |||
4d7a936721 | |||
886afab1ef | |||
0f6cd57f3d | |||
ffb8e8d506 | |||
c3867ccbd4 | |||
6bc7be6336 | |||
26d2ec68b8 | |||
5e0ff2891a | |||
1a0428a8e0 | |||
9267018d06 | |||
acec849428 | |||
1e9f2cbc2d | |||
9c460bab22 | |||
4e7705ce53 | |||
d5962bbf9e | |||
64669e5f58 | |||
f05f3fe37c | |||
896b0bf063 | |||
716bcc6fef | |||
31415c10a2 | |||
fe03807739 | |||
30d27a212e |
@@ -1,31 +0,0 @@
|
|||||||
name: Lint
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "**" # matches every branch
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint_and_typecheck:
|
|
||||||
name: Lint and Typecheck
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
cache: "pnpm"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Run check
|
|
||||||
run: pnpm run check
|
|
109
README.md
109
README.md
@@ -1,108 +1,3 @@
|
|||||||

|
# fire
|
||||||
|
|
||||||
# InvestingFIRE 🔥 — The #1 Interactive FIRE Calculator
|
FIRE calculator
|
||||||
|
|
||||||
**InvestingFIRE** is a responsive web application for calculating your path to Financial Independence and Early Retirement (FIRE). It features a year-by-year projection engine that simulates both accumulation (savings and investment growth) and retirement (withdrawals) phases, allowing users to:
|
|
||||||
|
|
||||||
- Input starting capital, monthly savings, expected annual growth rate, inflation rate, current age, desired retirement age, life expectancy, and desired monthly retirement allowance.
|
|
||||||
- View a dynamic chart displaying projected portfolio balance and monthly allowance over time.
|
|
||||||
- Instantly see their estimated “FIRE number” (required capital at retirement), how long their capital will last, and compare results to the “4% rule.”
|
|
||||||
- Adjust assumptions live, with all calculations and visualizations updating automatically.
|
|
||||||
- Access explanatory content about FIRE methodology, key variables, and additional community resources, all on a single, consolidated page.
|
|
||||||
|
|
||||||
The project’s code is structured using React/Next.js with TypeScript, focusing on user experience, modern UI components, and clarity of financial assumptions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Features at a Glance
|
|
||||||
|
|
||||||
- **⚡️ Real-Time Projections:** Every field updates the chart _as you type_. Experiment with savings, growth rates, inflation, or retirement age and see your future instantly.
|
|
||||||
- **📈 Interactive Chart:** Dual-area plots for portfolio value and future monthly spending, plus reference lines for FIRE milestones and “4% rule” legends.
|
|
||||||
- **🧠 Education Baked In:** Contextual tooltips, deep-dive sections on how FIRE works, FAQs, and must-read resources included.
|
|
||||||
- **🔎 Detailed Methodology:** Not just a 25x rule — runs a full, year-by-year simulation with inflation-adjusted withdrawals and optional 4%-rule overlays.
|
|
||||||
- **👌 Modern UX:** Typing, sliding, or clicking feels _good_. Responsive on all devices.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧰 How It Works
|
|
||||||
|
|
||||||
The calculator models your FIRE journey in two phases:
|
|
||||||
|
|
||||||
1. **Accumulation:**
|
|
||||||
- Your starting capital is grown by your expected CAGR (~7% by default).
|
|
||||||
- Monthly savings are added for each year until retirement.
|
|
||||||
- Every variable can be adjusted live (capital, savings, age, growth, inflation, spending, target retirement).
|
|
||||||
|
|
||||||
2. **Retirement:**
|
|
||||||
- Your balance continues to grow by CAGR.
|
|
||||||
- Each year, an inflation-adjusted monthly allowance is withdrawn.
|
|
||||||
- The simulation runs until your selected life expectancy, showing the possibility of portfolio depletion.
|
|
||||||
|
|
||||||
**Key Outputs:**
|
|
||||||
|
|
||||||
- 🔥 “FIRE Number”: Portfolio value at your defined retirement age
|
|
||||||
- 📊 Interactive projection chart: See how your nest egg and withdrawals evolve over time
|
|
||||||
- 4️⃣ “4% Rule” overlays: Compare dynamic results to classic rule-of-thumb
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🌟 Try It For Yourself
|
|
||||||
|
|
||||||
To run locally:
|
|
||||||
|
|
||||||
1. **Clone the repo**
|
|
||||||
```bash
|
|
||||||
git clone https://git.schulze.network/schulze/fire.git
|
|
||||||
cd fire
|
|
||||||
```
|
|
||||||
2. **Install dependencies**
|
|
||||||
```bash
|
|
||||||
pnpm install
|
|
||||||
```
|
|
||||||
3. **Run the app**
|
|
||||||
```bash
|
|
||||||
pnpm run dev
|
|
||||||
```
|
|
||||||
4. Visit [http://localhost:3000](http://localhost:3000) and unleash the fire.
|
|
||||||
|
|
||||||
Deployed version: [https://investingfire.com](https://investingfire.com)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✏️ Inputs & Variables
|
|
||||||
|
|
||||||
- **Starting Capital** — How much you’ve already invested
|
|
||||||
- **Monthly Savings** — What you’ll add each month
|
|
||||||
- **Current Age & Retirement Age** — Your FI timeline
|
|
||||||
- **Life Expectancy** — How long do you want income to last?
|
|
||||||
- **Expected Growth Rate (CAGR)** — Portfolio annual % return, before inflation
|
|
||||||
- **Inflation Rate** — Cost of living increases
|
|
||||||
- **Desired Monthly Allowance** — Your lifestyle, today’s dollars
|
|
||||||
|
|
||||||
As you adjust these, all projections update instantly _without needing to hit “Calculate.”_
|
|
||||||
|
|
||||||
Try many “what ifs” fast.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 👩💻 Contributing
|
|
||||||
|
|
||||||
Pull requests are welcome! Open issues for bugs, new features, or debate about safe withdrawal rates and tax assumptions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 License
|
|
||||||
|
|
||||||
[GPL-3.0](./LICENSE)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🥇 Why Use InvestingFIRE?
|
|
||||||
|
|
||||||
- You want the truth — not just a 4% fantasy.
|
|
||||||
- You want to learn, not just punch in numbers.
|
|
||||||
- You want clarity, speed, and modern UI.
|
|
||||||
- You want to show your friends the best FIRE tool on the web.
|
|
||||||
|
|
||||||
Enjoy the _rocket ride_ to financial independence.
|
|
||||||
**InvestingFIRE — Know your number. Change your future.**
|
|
7300
package-lock.json
generated
Normal file
7300
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@@ -6,12 +6,14 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"check": "next lint && tsc --noEmit",
|
"check": "next lint && tsc --noEmit",
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbo",
|
||||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||||
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||||
|
"lint": "next lint",
|
||||||
"lint:fix": "next lint --fix",
|
"lint:fix": "next lint --fix",
|
||||||
"preview": "next build && next start",
|
"preview": "next build && next start",
|
||||||
"start": "next start"
|
"start": "next start",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.0.1",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
@@ -20,38 +22,37 @@
|
|||||||
"@radix-ui/react-select": "^2.2.2",
|
"@radix-ui/react-select": "^2.2.2",
|
||||||
"@radix-ui/react-slider": "^1.3.2",
|
"@radix-ui/react-slider": "^1.3.2",
|
||||||
"@radix-ui/react-slot": "^1.2.0",
|
"@radix-ui/react-slot": "^1.2.0",
|
||||||
"@t3-oss/env-nextjs": "^0.13.0",
|
"@t3-oss/env-nextjs": "^0.12.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.503.0",
|
||||||
"next": "^15.4.1",
|
"next": "^15.2.3",
|
||||||
"next-plausible": "^3.12.4",
|
"next-plausible": "^3.12.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.56.1",
|
"react-hook-form": "^7.56.1",
|
||||||
"recharts": "^2.15.3",
|
"recharts": "^2.15.3",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"zod": "^4.0.0"
|
"zod": "^3.24.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@tailwindcss/postcss": "4.1.11",
|
"@tailwindcss/postcss": "^4.0.15",
|
||||||
"@types/node": "22.16.3",
|
"@types/node": "^20.14.10",
|
||||||
"@types/react": "19.1.8",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "19.1.6",
|
"@types/react-dom": "^19.0.0",
|
||||||
"eslint": "9.31.0",
|
"eslint": "^9.23.0",
|
||||||
"eslint-config-next": "15.4.1",
|
"eslint-config-next": "^15.2.3",
|
||||||
"eslint-plugin-react-hooks": "5.2.0",
|
"postcss": "^8.5.3",
|
||||||
"postcss": "8.5.6",
|
"prettier": "^3.5.3",
|
||||||
"prettier": "3.6.2",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"prettier-plugin-tailwindcss": "0.6.14",
|
"tailwindcss": "^4.0.15",
|
||||||
"tailwindcss": "4.1.11",
|
"tw-animate-css": "^1.2.8",
|
||||||
"tw-animate-css": "1.3.5",
|
"typescript": "^5.8.2",
|
||||||
"typescript": "5.8.3",
|
"typescript-eslint": "^8.27.0"
|
||||||
"typescript-eslint": "8.36.0"
|
|
||||||
},
|
},
|
||||||
"ct3aMetadata": {
|
"ct3aMetadata": {
|
||||||
"initVersion": "7.39.3"
|
"initVersion": "7.39.3"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.13.1"
|
"packageManager": "npm@11.2.0"
|
||||||
}
|
}
|
||||||
|
5055
pnpm-lock.yaml
generated
5055
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
|||||||
ignoredBuiltDependencies:
|
|
||||||
- unrs-resolver
|
|
||||||
|
|
||||||
onlyBuiltDependencies:
|
|
||||||
- '@tailwindcss/oxide'
|
|
||||||
- sharp
|
|
@@ -1 +0,0 @@
|
|||||||
wgu5fuk8d5j5wp3pjtta9vrw8d9by9qk
|
|
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["config:best-practices", ":semanticCommits"],
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"packageRules": [
|
|
||||||
{
|
|
||||||
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
|
|
||||||
"automerge": true,
|
|
||||||
"automergeType": "branch"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@@ -1,157 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import {
|
|
||||||
type LucideIcon,
|
|
||||||
HandCoins,
|
|
||||||
Bitcoin,
|
|
||||||
Coins,
|
|
||||||
DollarSign,
|
|
||||||
Euro,
|
|
||||||
IndianRupee,
|
|
||||||
JapaneseYen,
|
|
||||||
PiggyBank,
|
|
||||||
PoundSterling,
|
|
||||||
Wallet,
|
|
||||||
Banknote,
|
|
||||||
ChartCandlestick,
|
|
||||||
CirclePercent,
|
|
||||||
CreditCard,
|
|
||||||
Gem,
|
|
||||||
Receipt,
|
|
||||||
ShoppingBasket,
|
|
||||||
Rocket,
|
|
||||||
RockingChair,
|
|
||||||
Sparkles,
|
|
||||||
ChartPie,
|
|
||||||
ChartBar,
|
|
||||||
BarChart3,
|
|
||||||
ChartLine,
|
|
||||||
TrendingDown,
|
|
||||||
TrendingUp,
|
|
||||||
Vault,
|
|
||||||
Landmark,
|
|
||||||
Briefcase,
|
|
||||||
Handshake,
|
|
||||||
Shield,
|
|
||||||
Lock,
|
|
||||||
CalendarRange,
|
|
||||||
Hourglass,
|
|
||||||
Sprout,
|
|
||||||
Target,
|
|
||||||
} from "lucide-react";
|
|
||||||
|
|
||||||
export default function MultiIconPattern({ opacity = 0.2, spacing = 160 }) {
|
|
||||||
const [width, setWidth] = useState(0);
|
|
||||||
const [height, setHeight] = useState(0);
|
|
||||||
const [rows, setRows] = useState(0);
|
|
||||||
const [columns, setColumns] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const updateDimensions = () => {
|
|
||||||
if (window.innerWidth > width + spacing * 2) {
|
|
||||||
setWidth(window.innerWidth);
|
|
||||||
}
|
|
||||||
if (window.innerHeight > height + spacing * 2) {
|
|
||||||
setHeight(window.innerHeight);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
updateDimensions();
|
|
||||||
window.addEventListener("resize", updateDimensions);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("resize", updateDimensions);
|
|
||||||
};
|
|
||||||
}, [height, width, spacing]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setColumns(Math.ceil(width / spacing) + 3);
|
|
||||||
}, [width, spacing]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setRows(Math.ceil(height / spacing) + 3);
|
|
||||||
}, [height, spacing]);
|
|
||||||
|
|
||||||
// Explicitly type the array as LucideIcon[]
|
|
||||||
const iconComponents: LucideIcon[] = [
|
|
||||||
HandCoins,
|
|
||||||
Bitcoin,
|
|
||||||
Coins,
|
|
||||||
DollarSign,
|
|
||||||
Euro,
|
|
||||||
IndianRupee,
|
|
||||||
JapaneseYen,
|
|
||||||
PiggyBank,
|
|
||||||
PoundSterling,
|
|
||||||
Wallet,
|
|
||||||
Banknote,
|
|
||||||
ChartCandlestick,
|
|
||||||
CirclePercent,
|
|
||||||
CreditCard,
|
|
||||||
Gem,
|
|
||||||
Receipt,
|
|
||||||
ShoppingBasket,
|
|
||||||
Rocket,
|
|
||||||
RockingChair,
|
|
||||||
Sparkles,
|
|
||||||
ChartPie,
|
|
||||||
ChartBar,
|
|
||||||
BarChart3,
|
|
||||||
ChartLine,
|
|
||||||
TrendingDown,
|
|
||||||
TrendingUp,
|
|
||||||
Vault,
|
|
||||||
Landmark,
|
|
||||||
Briefcase,
|
|
||||||
Handshake,
|
|
||||||
Shield,
|
|
||||||
Lock,
|
|
||||||
CalendarRange,
|
|
||||||
Hourglass,
|
|
||||||
Sprout,
|
|
||||||
Target,
|
|
||||||
];
|
|
||||||
|
|
||||||
const renderIcons = ({
|
|
||||||
rows,
|
|
||||||
columns,
|
|
||||||
}: {
|
|
||||||
rows: number;
|
|
||||||
columns: number;
|
|
||||||
}) => {
|
|
||||||
const icons = [];
|
|
||||||
for (let y = 0; y < rows; y++) {
|
|
||||||
for (let x = 0; x < columns; x++) {
|
|
||||||
// Pick a random icon component from the array
|
|
||||||
const randomIndex = Math.floor(Math.random() * iconComponents.length);
|
|
||||||
const IconComponent = iconComponents[randomIndex]!;
|
|
||||||
|
|
||||||
// Slightly randomize size and position for more organic feel
|
|
||||||
const size = 28 + Math.floor(Math.random() * 8);
|
|
||||||
const xOffset = Math.floor(Math.random() * (spacing / 1.618));
|
|
||||||
const yOffset = Math.floor(Math.random() * (spacing / 1.618));
|
|
||||||
|
|
||||||
icons.push(
|
|
||||||
<IconComponent
|
|
||||||
key={`icon-${x}-${y}`}
|
|
||||||
size={size}
|
|
||||||
className="text-primary fixed"
|
|
||||||
style={{
|
|
||||||
left: `${x * spacing + xOffset}px`,
|
|
||||||
top: `${y * spacing + yOffset}px`,
|
|
||||||
opacity: opacity,
|
|
||||||
transform: `rotate(${Math.round((Math.random() - 0.5) * 30)}deg)`,
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return icons;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="absolute h-full w-full">
|
|
||||||
{width > 0 && renderIcons({ rows, columns })}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -73,16 +73,12 @@ interface YearlyData {
|
|||||||
age: number;
|
age: number;
|
||||||
year: number;
|
year: number;
|
||||||
balance: number;
|
balance: number;
|
||||||
untouchedBalance: number;
|
|
||||||
phase: "accumulation" | "retirement";
|
phase: "accumulation" | "retirement";
|
||||||
monthlyAllowance: number;
|
monthlyAllowance: number;
|
||||||
untouchedMonthlyAllowance: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CalculationResult {
|
interface CalculationResult {
|
||||||
fireNumber: number | null;
|
fireNumber: number | null;
|
||||||
fireNumber4percent: number | null;
|
|
||||||
retirementAge4percent: number | null;
|
|
||||||
yearlyData: YearlyData[];
|
yearlyData: YearlyData[];
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
@@ -105,8 +101,8 @@ const tooltipRenderer = ({
|
|||||||
return (
|
return (
|
||||||
<div className="bg-background border p-2 shadow-sm">
|
<div className="bg-background border p-2 shadow-sm">
|
||||||
<p className="font-medium">{`Year: ${data.year.toString()} (Age: ${data.age.toString()})`}</p>
|
<p className="font-medium">{`Year: ${data.year.toString()} (Age: ${data.age.toString()})`}</p>
|
||||||
<p className="text-orange-500">{`Balance: ${formatNumber(data.balance)}`}</p>
|
<p className="text-chart-1">{`Balance: ${formatNumber(data.balance)}`}</p>
|
||||||
<p className="text-red-600">{`Monthly allowance: ${formatNumber(data.monthlyAllowance)}`}</p>
|
<p className="text-chart-2">{`Monthly allowance: ${formatNumber(data.monthlyAllowance)}`}</p>
|
||||||
<p>{`Phase: ${data.phase === "accumulation" ? "Accumulation" : "Retirement"}`}</p>
|
<p>{`Phase: ${data.phase === "accumulation" ? "Accumulation" : "Retirement"}`}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -117,10 +113,9 @@ const tooltipRenderer = ({
|
|||||||
export default function FireCalculatorForm() {
|
export default function FireCalculatorForm() {
|
||||||
const [result, setResult] = useState<CalculationResult | null>(null);
|
const [result, setResult] = useState<CalculationResult | null>(null);
|
||||||
const irlYear = new Date().getFullYear();
|
const irlYear = new Date().getFullYear();
|
||||||
const [showing4percent, setShowing4percent] = useState(false);
|
|
||||||
|
|
||||||
// Initialize form with default values
|
// Initialize form with default values
|
||||||
const form = useForm<z.input<typeof formSchema>, undefined, FormValues>({
|
const form = useForm<FormValues>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
startingCapital: 50000,
|
startingCapital: 50000,
|
||||||
@@ -128,7 +123,7 @@ export default function FireCalculatorForm() {
|
|||||||
currentAge: 25,
|
currentAge: 25,
|
||||||
cagr: 7,
|
cagr: 7,
|
||||||
desiredMonthlyAllowance: 3000,
|
desiredMonthlyAllowance: 3000,
|
||||||
inflationRate: 2.3,
|
inflationRate: 2,
|
||||||
lifeExpectancy: 84,
|
lifeExpectancy: 84,
|
||||||
retirementAge: 55,
|
retirementAge: 55,
|
||||||
},
|
},
|
||||||
@@ -154,10 +149,8 @@ export default function FireCalculatorForm() {
|
|||||||
age: age,
|
age: age,
|
||||||
year: irlYear,
|
year: irlYear,
|
||||||
balance: startingCapital,
|
balance: startingCapital,
|
||||||
untouchedBalance: startingCapital,
|
|
||||||
phase: "accumulation",
|
phase: "accumulation",
|
||||||
monthlyAllowance: 0,
|
monthlyAllowance: initialMonthlyAllowance,
|
||||||
untouchedMonthlyAllowance: initialMonthlyAllowance,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calculate accumulation phase (before retirement)
|
// Calculate accumulation phase (before retirement)
|
||||||
@@ -182,18 +175,13 @@ export default function FireCalculatorForm() {
|
|||||||
newBalance =
|
newBalance =
|
||||||
previousYearData.balance * annualGrowthRate - inflatedAllowance * 12;
|
previousYearData.balance * annualGrowthRate - inflatedAllowance * 12;
|
||||||
}
|
}
|
||||||
const untouchedBalance =
|
|
||||||
previousYearData.untouchedBalance * annualGrowthRate +
|
|
||||||
monthlySavings * 12;
|
|
||||||
const allowance = phase === "retirement" ? inflatedAllowance : 0;
|
|
||||||
yearlyData.push({
|
yearlyData.push({
|
||||||
age: currentAge,
|
age: currentAge,
|
||||||
year: year,
|
year: year,
|
||||||
balance: newBalance,
|
balance: newBalance,
|
||||||
untouchedBalance: untouchedBalance,
|
|
||||||
phase: phase,
|
phase: phase,
|
||||||
monthlyAllowance: allowance,
|
monthlyAllowance: inflatedAllowance,
|
||||||
untouchedMonthlyAllowance: inflatedAllowance,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,23 +192,9 @@ export default function FireCalculatorForm() {
|
|||||||
);
|
);
|
||||||
const retirementData = yearlyData[retirementIndex];
|
const retirementData = yearlyData[retirementIndex];
|
||||||
|
|
||||||
const [fireNumber4percent, retirementAge4percent] = (() => {
|
|
||||||
for (const yearData of yearlyData) {
|
|
||||||
if (
|
|
||||||
yearData.untouchedBalance >
|
|
||||||
(yearData.untouchedMonthlyAllowance * 12) / 0.04
|
|
||||||
) {
|
|
||||||
return [yearData.untouchedBalance, yearData.age];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [0, 0];
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (retirementIndex === -1 || !retirementData) {
|
if (retirementIndex === -1 || !retirementData) {
|
||||||
setResult({
|
setResult({
|
||||||
fireNumber: null,
|
fireNumber: null,
|
||||||
fireNumber4percent: null,
|
|
||||||
retirementAge4percent: null,
|
|
||||||
error: "Could not calculate retirement data",
|
error: "Could not calculate retirement data",
|
||||||
yearlyData: yearlyData,
|
yearlyData: yearlyData,
|
||||||
});
|
});
|
||||||
@@ -228,8 +202,6 @@ export default function FireCalculatorForm() {
|
|||||||
// Set the result
|
// Set the result
|
||||||
setResult({
|
setResult({
|
||||||
fireNumber: retirementData.balance,
|
fireNumber: retirementData.balance,
|
||||||
fireNumber4percent: fireNumber4percent,
|
|
||||||
retirementAge4percent: retirementAge4percent,
|
|
||||||
yearlyData: yearlyData,
|
yearlyData: yearlyData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -258,18 +230,11 @@ export default function FireCalculatorForm() {
|
|||||||
<Input
|
<Input
|
||||||
placeholder="e.g., 10000"
|
placeholder="e.g., 10000"
|
||||||
type="number"
|
type="number"
|
||||||
value={field.value as number | string | undefined}
|
{...field}
|
||||||
onChange={(e) => {
|
onChange={(value) => {
|
||||||
field.onChange(
|
field.onChange(value);
|
||||||
e.target.value === ""
|
|
||||||
? undefined
|
|
||||||
: Number(e.target.value),
|
|
||||||
);
|
|
||||||
void form.handleSubmit(onSubmit)();
|
void form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
|
||||||
name={field.name}
|
|
||||||
ref={field.ref}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -286,18 +251,11 @@ export default function FireCalculatorForm() {
|
|||||||
<Input
|
<Input
|
||||||
placeholder="e.g., 500"
|
placeholder="e.g., 500"
|
||||||
type="number"
|
type="number"
|
||||||
value={field.value as number | string | undefined}
|
{...field}
|
||||||
onChange={(e) => {
|
onChange={(value) => {
|
||||||
field.onChange(
|
field.onChange(value);
|
||||||
e.target.value === ""
|
|
||||||
? undefined
|
|
||||||
: Number(e.target.value),
|
|
||||||
);
|
|
||||||
void form.handleSubmit(onSubmit)();
|
void form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
|
||||||
name={field.name}
|
|
||||||
ref={field.ref}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -314,18 +272,11 @@ export default function FireCalculatorForm() {
|
|||||||
<Input
|
<Input
|
||||||
placeholder="e.g., 30"
|
placeholder="e.g., 30"
|
||||||
type="number"
|
type="number"
|
||||||
value={field.value as number | string | undefined}
|
{...field}
|
||||||
onChange={(e) => {
|
onChange={(value) => {
|
||||||
field.onChange(
|
field.onChange(value);
|
||||||
e.target.value === ""
|
|
||||||
? undefined
|
|
||||||
: Number(e.target.value),
|
|
||||||
);
|
|
||||||
void form.handleSubmit(onSubmit)();
|
void form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
|
||||||
name={field.name}
|
|
||||||
ref={field.ref}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -342,18 +293,11 @@ export default function FireCalculatorForm() {
|
|||||||
<Input
|
<Input
|
||||||
placeholder="e.g., 90"
|
placeholder="e.g., 90"
|
||||||
type="number"
|
type="number"
|
||||||
value={field.value as number | string | undefined}
|
{...field}
|
||||||
onChange={(e) => {
|
onChange={(value) => {
|
||||||
field.onChange(
|
field.onChange(value);
|
||||||
e.target.value === ""
|
|
||||||
? undefined
|
|
||||||
: Number(e.target.value),
|
|
||||||
);
|
|
||||||
void form.handleSubmit(onSubmit)();
|
void form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
|
||||||
name={field.name}
|
|
||||||
ref={field.ref}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -371,18 +315,11 @@ export default function FireCalculatorForm() {
|
|||||||
placeholder="e.g., 7"
|
placeholder="e.g., 7"
|
||||||
type="number"
|
type="number"
|
||||||
step="0.1"
|
step="0.1"
|
||||||
value={field.value as number | string | undefined}
|
{...field}
|
||||||
onChange={(e) => {
|
onChange={(value) => {
|
||||||
field.onChange(
|
field.onChange(value);
|
||||||
e.target.value === ""
|
|
||||||
? undefined
|
|
||||||
: Number(e.target.value),
|
|
||||||
);
|
|
||||||
void form.handleSubmit(onSubmit)();
|
void form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
|
||||||
name={field.name}
|
|
||||||
ref={field.ref}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -400,18 +337,11 @@ export default function FireCalculatorForm() {
|
|||||||
placeholder="e.g., 2"
|
placeholder="e.g., 2"
|
||||||
type="number"
|
type="number"
|
||||||
step="0.1"
|
step="0.1"
|
||||||
value={field.value as number | string | undefined}
|
{...field}
|
||||||
onChange={(e) => {
|
onChange={(value) => {
|
||||||
field.onChange(
|
field.onChange(value);
|
||||||
e.target.value === ""
|
|
||||||
? undefined
|
|
||||||
: Number(e.target.value),
|
|
||||||
);
|
|
||||||
void form.handleSubmit(onSubmit)();
|
void form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
|
||||||
name={field.name}
|
|
||||||
ref={field.ref}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -430,18 +360,11 @@ export default function FireCalculatorForm() {
|
|||||||
<Input
|
<Input
|
||||||
placeholder="e.g., 2000"
|
placeholder="e.g., 2000"
|
||||||
type="number"
|
type="number"
|
||||||
value={field.value as number | string | undefined}
|
{...field}
|
||||||
onChange={(e) => {
|
onChange={(value) => {
|
||||||
field.onChange(
|
field.onChange(value);
|
||||||
e.target.value === ""
|
|
||||||
? undefined
|
|
||||||
: Number(e.target.value),
|
|
||||||
);
|
|
||||||
void form.handleSubmit(onSubmit)();
|
void form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
|
||||||
name={field.name}
|
|
||||||
ref={field.ref}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -455,18 +378,16 @@ export default function FireCalculatorForm() {
|
|||||||
name="retirementAge"
|
name="retirementAge"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>Retirement Age: {field.value}</FormLabel>
|
||||||
Retirement Age: {field.value as number}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Slider
|
<Slider
|
||||||
name="retirementAge"
|
name="retirementAge"
|
||||||
value={[field.value as number]}
|
value={[field.value]}
|
||||||
min={25}
|
min={18}
|
||||||
max={75}
|
max={form.getValues("lifeExpectancy")}
|
||||||
step={1}
|
step={1}
|
||||||
onValueChange={(value: number[]) => {
|
onValueChange={(value) => {
|
||||||
field.onChange(value[0]);
|
field.onChange(...value);
|
||||||
void form.handleSubmit(onSubmit)();
|
void form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
className="py-4"
|
className="py-4"
|
||||||
@@ -491,14 +412,23 @@ export default function FireCalculatorForm() {
|
|||||||
Projected balance growth with your selected retirement age
|
Projected balance growth with your selected retirement age
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="px-2">
|
<CardContent>
|
||||||
<ChartContainer
|
<ChartContainer
|
||||||
className="aspect-auto h-80 w-full"
|
className="aspect-auto h-80 w-full"
|
||||||
config={{}}
|
config={{
|
||||||
|
balance: {
|
||||||
|
label: "Balance",
|
||||||
|
color: "var(--chart-1)",
|
||||||
|
},
|
||||||
|
realBalance: {
|
||||||
|
label: "Real Balance",
|
||||||
|
color: "var(--chart-3)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<AreaChart
|
<AreaChart
|
||||||
data={result.yearlyData}
|
data={result.yearlyData}
|
||||||
margin={{ top: 10, right: 20, left: 20, bottom: 10 }}
|
margin={{ top: 20, right: 30, left: 20, bottom: 20 }}
|
||||||
>
|
>
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<XAxis
|
<XAxis
|
||||||
@@ -509,10 +439,7 @@ export default function FireCalculatorForm() {
|
|||||||
offset: -10,
|
offset: -10,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* Right Y axis */}
|
|
||||||
<YAxis
|
<YAxis
|
||||||
yAxisId={"right"}
|
|
||||||
orientation="right"
|
|
||||||
tickFormatter={(value: number) => {
|
tickFormatter={(value: number) => {
|
||||||
if (value >= 1000000) {
|
if (value >= 1000000) {
|
||||||
return `${(value / 1000000).toPrecision(3)}M`;
|
return `${(value / 1000000).toPrecision(3)}M`;
|
||||||
@@ -525,24 +452,7 @@ export default function FireCalculatorForm() {
|
|||||||
}
|
}
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}}
|
}}
|
||||||
width={30}
|
width={25}
|
||||||
stroke="var(--color-orange-500)"
|
|
||||||
tick={{}}
|
|
||||||
/>
|
|
||||||
{/* Left Y axis */}
|
|
||||||
<YAxis
|
|
||||||
yAxisId="left"
|
|
||||||
orientation="left"
|
|
||||||
tickFormatter={(value: number) => {
|
|
||||||
if (value >= 1000000) {
|
|
||||||
return `${(value / 1000000).toPrecision(3)}M`;
|
|
||||||
} else if (value >= 1000) {
|
|
||||||
return `${(value / 1000).toPrecision(3)}K`;
|
|
||||||
}
|
|
||||||
return value.toString();
|
|
||||||
}}
|
|
||||||
width={30}
|
|
||||||
stroke="var(--color-red-600)"
|
|
||||||
/>
|
/>
|
||||||
<ChartTooltip content={tooltipRenderer} />
|
<ChartTooltip content={tooltipRenderer} />
|
||||||
<defs>
|
<defs>
|
||||||
@@ -555,12 +465,30 @@ export default function FireCalculatorForm() {
|
|||||||
>
|
>
|
||||||
<stop
|
<stop
|
||||||
offset="5%"
|
offset="5%"
|
||||||
stopColor="var(--color-orange-500)"
|
stopColor="var(--chart-1)"
|
||||||
stopOpacity={0.8}
|
stopOpacity={0.8}
|
||||||
/>
|
/>
|
||||||
<stop
|
<stop
|
||||||
offset="95%"
|
offset="95%"
|
||||||
stopColor="var(--color-orange-500)"
|
stopColor="var(--chart-1)"
|
||||||
|
stopOpacity={0.1}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="fillAllowance"
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="0"
|
||||||
|
y2="1"
|
||||||
|
>
|
||||||
|
<stop
|
||||||
|
offset="5%"
|
||||||
|
stopColor="var(--chart-2)"
|
||||||
|
stopOpacity={0.8}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="95%"
|
||||||
|
stopColor="var(--chart-2)"
|
||||||
stopOpacity={0.1}
|
stopOpacity={0.1}
|
||||||
/>
|
/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
@@ -569,92 +497,50 @@ export default function FireCalculatorForm() {
|
|||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey="balance"
|
dataKey="balance"
|
||||||
name="balance"
|
name="balance"
|
||||||
stroke="var(--color-orange-500)"
|
stroke="var(--chart-1)"
|
||||||
fill="url(#fillBalance)"
|
fill="url(#fillBalance)"
|
||||||
fillOpacity={0.9}
|
fillOpacity={0.4}
|
||||||
activeDot={{ r: 6 }}
|
activeDot={{ r: 6 }}
|
||||||
yAxisId={"right"}
|
|
||||||
stackId={"a"}
|
|
||||||
/>
|
/>
|
||||||
<Area
|
<Area
|
||||||
type="step"
|
type="monotone"
|
||||||
dataKey="monthlyAllowance"
|
dataKey="monthlyAllowance"
|
||||||
name="allowance"
|
name="allowance"
|
||||||
stroke="var(--color-red-600)"
|
stroke="var(--chart-2)"
|
||||||
fill="none"
|
fill="url(#fillAllowance)"
|
||||||
|
fillOpacity={0.4}
|
||||||
activeDot={{ r: 6 }}
|
activeDot={{ r: 6 }}
|
||||||
yAxisId="left"
|
|
||||||
/>
|
/>
|
||||||
{result.fireNumber && (
|
{result.fireNumber && (
|
||||||
<ReferenceLine
|
<ReferenceLine
|
||||||
y={result.fireNumber}
|
y={result.fireNumber}
|
||||||
stroke="var(--primary)"
|
stroke="var(--chart-3)"
|
||||||
strokeWidth={2}
|
strokeWidth={1}
|
||||||
strokeDasharray="2 1"
|
strokeDasharray="2 2"
|
||||||
label={{
|
label={{
|
||||||
value: "FIRE Number",
|
value: "FIRE Number",
|
||||||
position: "insideBottomRight",
|
position: "insideBottomRight",
|
||||||
}}
|
}}
|
||||||
yAxisId={"right"}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{result.fireNumber4percent && showing4percent && (
|
|
||||||
<ReferenceLine
|
|
||||||
y={result.fireNumber4percent}
|
|
||||||
stroke="var(--secondary)"
|
|
||||||
strokeWidth={1}
|
|
||||||
strokeDasharray="1 1"
|
|
||||||
label={{
|
|
||||||
value: "4%-Rule FIRE Number",
|
|
||||||
position: "insideBottomLeft",
|
|
||||||
}}
|
|
||||||
yAxisId={"right"}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ReferenceLine
|
<ReferenceLine
|
||||||
x={
|
x={
|
||||||
irlYear +
|
irlYear +
|
||||||
(Number(form.getValues("retirementAge")) -
|
(form.getValues("retirementAge") -
|
||||||
Number(form.getValues("currentAge")))
|
form.getValues("currentAge"))
|
||||||
}
|
}
|
||||||
stroke="var(--primary)"
|
stroke="var(--chart-2)"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
label={{
|
label={{
|
||||||
value: "Retirement",
|
value: "Retirement",
|
||||||
position: "insideTopRight",
|
position: "insideTopRight",
|
||||||
}}
|
}}
|
||||||
yAxisId={"left"}
|
|
||||||
/>
|
/>
|
||||||
{result.retirementAge4percent && showing4percent && (
|
|
||||||
<ReferenceLine
|
|
||||||
x={
|
|
||||||
irlYear +
|
|
||||||
(result.retirementAge4percent -
|
|
||||||
Number(form.getValues("currentAge")))
|
|
||||||
}
|
|
||||||
stroke="var(--secondary)"
|
|
||||||
strokeWidth={1}
|
|
||||||
label={{
|
|
||||||
value: "4%-Rule Retirement",
|
|
||||||
position: "insideBottomLeft",
|
|
||||||
}}
|
|
||||||
yAxisId={"left"}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
{result && (
|
|
||||||
<Button
|
|
||||||
onClick={() => setShowing4percent(!showing4percent)}
|
|
||||||
variant={showing4percent ? "secondary" : "default"}
|
|
||||||
size={"sm"}
|
|
||||||
>
|
|
||||||
{showing4percent ? "Hide" : "Show"} 4%-Rule
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -693,45 +579,11 @@ export default function FireCalculatorForm() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<p className="text-3xl font-bold">
|
<p className="text-3xl font-bold">
|
||||||
{Number(form.getValues("lifeExpectancy")) -
|
{form.getValues("lifeExpectancy") -
|
||||||
Number(form.getValues("retirementAge"))}
|
form.getValues("retirementAge")}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
{showing4percent && (
|
|
||||||
<>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>4%-Rule FIRE Number</CardTitle>
|
|
||||||
<CardDescription className="text-xs">
|
|
||||||
Capital needed for 4% of it to be greater than your
|
|
||||||
yearly allowance
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<p className="text-3xl font-bold">
|
|
||||||
{formatNumber(result.fireNumber4percent)}
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>4%-Rule Retirement Duration</CardTitle>
|
|
||||||
<CardDescription className="text-xs">
|
|
||||||
Years to enjoy your financial independence if you follow
|
|
||||||
the 4% rule
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<p className="text-3xl font-bold">
|
|
||||||
{Number(form.getValues("lifeExpectancy")) -
|
|
||||||
(result.retirementAge4percent ?? 0)}
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,16 +0,0 @@
|
|||||||
export default function Footer() {
|
|
||||||
return (
|
|
||||||
<footer className="w-full py-8 text-center text-xs">
|
|
||||||
<p className="text-xs">
|
|
||||||
© {new Date().getFullYear()} InvestingFIRE. All rights reserved.{" "}
|
|
||||||
<a
|
|
||||||
href="https://schulze.network"
|
|
||||||
target="_blank"
|
|
||||||
className="text-primary hover:underline"
|
|
||||||
>
|
|
||||||
Hosting by Schulze.network
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
);
|
|
||||||
}
|
|
BIN
src/app/favicon.ico
(Stored with Git LFS)
BIN
src/app/favicon.ico
(Stored with Git LFS)
Binary file not shown.
@@ -1,17 +1,15 @@
|
|||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
import PlausibleProvider from "next-plausible";
|
import PlausibleProvider from "next-plausible";
|
||||||
import { type Metadata, type Viewport } from "next";
|
import { type Metadata } from "next";
|
||||||
import { Geist } from "next/font/google";
|
import { Geist } from "next/font/google";
|
||||||
import { WebVitals } from "./components/web-vitals";
|
import { WebVitals } from "./components/web-vitals";
|
||||||
|
|
||||||
export const viewport: Viewport = {
|
|
||||||
themeColor: [{ color: "oklch(0.97 0.0228 95.96)" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "InvestingFIRE | Finance and Retirement Calculator",
|
title:
|
||||||
|
"InvestingFIRE Calculator | Plan Your Financial Independence & Early Retirement",
|
||||||
description:
|
description:
|
||||||
"Achieve Financial Independence & Early Retirement! Plan your FIRE journey with the InvestingFIRE calculator and get personalized projections in buttersmooth graphs.",
|
"Achieve Financial Independence, Retire Early (FIRE) with the InvestingFIRE calculator. Get personalized projections and investing advice to plan your journey.",
|
||||||
|
icons: [{ rel: "icon", url: "/favicon.ico" }],
|
||||||
};
|
};
|
||||||
|
|
||||||
const geist = Geist({
|
const geist = Geist({
|
||||||
@@ -26,16 +24,17 @@ export default function RootLayout({
|
|||||||
<html lang="en" className={geist.variable}>
|
<html lang="en" className={geist.variable}>
|
||||||
<head>
|
<head>
|
||||||
<meta name="apple-mobile-web-app-title" content="FIRE" />
|
<meta name="apple-mobile-web-app-title" content="FIRE" />
|
||||||
|
</head>
|
||||||
<PlausibleProvider
|
<PlausibleProvider
|
||||||
domain="investingfire.com"
|
domain="investingfire.com"
|
||||||
customDomain="https://analytics.schulze.network"
|
customDomain="https://analytics.schulze.network"
|
||||||
selfHosted={true}
|
selfHosted={true}
|
||||||
enabled={true}
|
enabled={true}
|
||||||
trackOutboundLinks={true}
|
trackOutboundLinks={true}
|
||||||
/>
|
>
|
||||||
</head>
|
|
||||||
<WebVitals />
|
<WebVitals />
|
||||||
<body>{children}</body>
|
<body>{children}</body>
|
||||||
|
</PlausibleProvider>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
355
src/app/page.tsx
355
src/app/page.tsx
@@ -6,18 +6,13 @@ import {
|
|||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
import Footer from "./components/footer";
|
|
||||||
import BackgroundPattern from "./components/BackgroundPattern";
|
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
<main className="text-primary-foreground to-destructive from-secondary flex min-h-screen flex-col items-center bg-gradient-to-b p-2">
|
<main className="text-primary-foreground to-destructive from-secondary flex min-h-screen flex-col items-center bg-gradient-to-b p-4">
|
||||||
<BackgroundPattern />
|
<div className="mx-auto flex flex-col items-center justify-center gap-4 text-center">
|
||||||
<div className="z-10 mx-auto flex flex-col items-center justify-center gap-4 text-center">
|
<div className="flex flex-row flex-wrap items-center justify-center gap-4 align-middle">
|
||||||
<div className="mt-8 flex flex-row flex-wrap items-center justify-center gap-4 align-middle">
|
|
||||||
<Image
|
<Image
|
||||||
priority
|
|
||||||
unoptimized
|
|
||||||
src="/investingfire_logo_no-bg.svg"
|
src="/investingfire_logo_no-bg.svg"
|
||||||
alt="InvestingFIRE Logo"
|
alt="InvestingFIRE Logo"
|
||||||
width={100}
|
width={100}
|
||||||
@@ -36,35 +31,29 @@ export default function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Added SEO Content Sections */}
|
{/* Added SEO Content Sections */}
|
||||||
<div className="z-10 mx-auto max-w-2xl py-12 text-left">
|
<div className="mx-auto max-w-2xl py-12 text-left">
|
||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<h2 className="mb-4 text-3xl font-bold">
|
<h2 className="mb-4 text-3xl font-bold">
|
||||||
What Is FIRE? Understanding Financial Independence and Early
|
What is FIRE? Understanding Financial Independence and Early
|
||||||
Retirement
|
Retirement
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mb-4 text-lg leading-relaxed">
|
<p className="mb-4 text-lg leading-relaxed">
|
||||||
FIRE stands for{" "}
|
FIRE stands for "Financial Independence, Retire Early."
|
||||||
<strong>Financial Independence, Retire Early</strong>. It's a
|
It's a movement focused on aggressive saving and strategic
|
||||||
lifestyle movement built around two core ideas:
|
investing to build a substantial portfolio. The goal is for
|
||||||
|
investment returns to cover living expenses indefinitely, freeing
|
||||||
|
you from traditional employment. Achieving FIRE means gaining the
|
||||||
|
freedom to pursue passions, travel, or simply enjoy life without
|
||||||
|
needing a regular paycheck. Sound investing advice is crucial for
|
||||||
|
building the wealth needed.
|
||||||
</p>
|
</p>
|
||||||
<ul className="mb-4 ml-6 list-disc space-y-2 text-lg">
|
|
||||||
<li>
|
|
||||||
<strong>Aggressive saving & investing</strong>—often 50%+ of
|
|
||||||
income—so your capital grows rapidly.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Passive-income coverage</strong>—when your investment
|
|
||||||
returns exceed your living expenses, you gain freedom from a
|
|
||||||
traditional 9-5.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p className="text-lg leading-relaxed">
|
<p className="text-lg leading-relaxed">
|
||||||
By reaching your personal <em>FIRE Number</em>—the nest egg needed
|
The core principle involves maximizing your savings rate (often
|
||||||
to cover your inflation-adjusted spending—you unlock the option to
|
50%+) and investing wisely, typically in low-cost, diversified
|
||||||
step away from a daily paycheck and pursue passion projects, travel,
|
assets like index funds. Your "FIRE number" – the capital
|
||||||
family, or anything else. This calculator helps you simulate your
|
needed – is often estimated as 25 times your desired annual
|
||||||
journey, estimate how much you need, and visualize both your
|
spending, derived from the 4% safe withdrawal rate guideline. This
|
||||||
accumulation phase and your retirement withdrawals over time.
|
FIRE calculator helps you personalize this estimate.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -73,58 +62,51 @@ export default function HomePage() {
|
|||||||
How This FIRE Calculator Provides Investing Insights
|
How This FIRE Calculator Provides Investing Insights
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mb-4 text-lg leading-relaxed">
|
<p className="mb-4 text-lg leading-relaxed">
|
||||||
Our interactive tool goes beyond a simple “25x annual spending”
|
This calculator helps visualize your path to FIRE by projecting
|
||||||
rule. It runs a <strong>year-by-year simulation</strong> of your
|
investment growth based on your inputs. Understanding these
|
||||||
portfolio, combining:
|
projections is a key piece of investing advice for long-term
|
||||||
|
planning. Here's a breakdown of the inputs:
|
||||||
</p>
|
</p>
|
||||||
<ul className="mb-4 ml-6 list-disc space-y-2 text-lg">
|
<ul className="mb-4 ml-6 list-disc space-y-2 text-lg">
|
||||||
<li>
|
<li>
|
||||||
<strong>Starting Capital</strong>—your current invested balance
|
<strong>Starting Capital:</strong> The total amount you currently
|
||||||
|
have invested.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Monthly Savings</strong>—ongoing contributions to your
|
<strong>Monthly Savings:</strong> The amount you consistently save
|
||||||
portfolio
|
and invest each month.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Expected Annual Growth Rate (CAGR)</strong>—compounding
|
<strong>Current Age:</strong> Your current age in years.
|
||||||
returns before inflation
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Annual Inflation Rate</strong>—to inflate your target
|
<strong>Expected Annual Growth Rate (%):</strong> The average
|
||||||
withdrawal each year
|
annual return you expect from your investments (after fees, before
|
||||||
|
inflation).
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Desired Monthly Allowance</strong>—today's-value
|
<strong>Desired Monthly Allowance (Today's Value):</strong>{" "}
|
||||||
spending goal
|
How much you want to be able to spend each month in retirement, in
|
||||||
|
today's money value.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Retirement Age & Life Expectancy</strong>—defines your
|
<strong>Annual Inflation Rate (%):</strong> The expected average
|
||||||
accumulation horizon and payout period
|
rate at which the cost of living will increase.
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p className="text-lg leading-relaxed">Key features:</p>
|
|
||||||
<ul className="mb-4 ml-6 list-disc space-y-2 text-lg">
|
|
||||||
<li>
|
|
||||||
<strong>Real-time calculation</strong>—as you tweak any input,
|
|
||||||
your FIRE Number and chart update instantly.
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Interactive chart</strong> with area plots for both{" "}
|
<strong>Life Expectancy (Age):</strong> The age until which you
|
||||||
<em>portfolio balance</em> and{" "}
|
want your funds to last.
|
||||||
<em>inflation-adjusted allowance</em>, plus reference lines
|
|
||||||
showing your retirement date and required FIRE Number.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Custom simulation</strong>—switches from accumulation
|
|
||||||
(adding savings) to retirement (withdrawing allowance),
|
|
||||||
compounding each year based on your growth rate.
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p className="text-lg leading-relaxed">
|
<p className="text-lg leading-relaxed">
|
||||||
With this level of granularity, you can confidently experiment with
|
The calculator simulates your investment growth year by year,
|
||||||
savings rate, target retirement age, and investment assumptions to
|
incorporating monthly contributions, the power of compound growth (a
|
||||||
discover how small tweaks speed up or delay your path to financial
|
core investing principle), and inflation's impact on your
|
||||||
independence.
|
target allowance. It estimates the age at which your capital could
|
||||||
|
sustain your desired, inflation-adjusted monthly spending throughout
|
||||||
|
your expected retirement until your specified life expectancy. It
|
||||||
|
calculates your potential "FIRE Number" and the age you
|
||||||
|
might reach financial independence.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -135,106 +117,86 @@ export default function HomePage() {
|
|||||||
<Accordion type="single" collapsible className="w-full">
|
<Accordion type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-1">
|
<AccordionItem value="item-1">
|
||||||
<AccordionTrigger className="text-xl font-semibold">
|
<AccordionTrigger className="text-xl font-semibold">
|
||||||
What methodology does this calculator use?
|
What is the 4% rule?
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="text-lg leading-relaxed">
|
<AccordionContent className="text-lg leading-relaxed">
|
||||||
We run a multi-year projection in two phases:
|
The 4% rule is a guideline suggesting that you can safely
|
||||||
<ol className="ml-6 list-decimal space-y-1">
|
withdraw 4% of your investment portfolio's value in your
|
||||||
<li>
|
first year of retirement, and then adjust that amount for
|
||||||
<strong>Accumulation:</strong> Your balance grows by CAGR
|
inflation each subsequent year, with a high probability of your
|
||||||
and you add monthly savings.
|
money lasting for at least 30 years. This calculator uses a more
|
||||||
</li>
|
dynamic simulation based on your life expectancy but is related
|
||||||
<li>
|
to this concept.
|
||||||
<strong>Retirement:</strong> The balance continues
|
|
||||||
compounding, but you withdraw an inflation-adjusted monthly
|
|
||||||
allowance.
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
The result: a precise estimate of the capital you'll have
|
|
||||||
at retirement (your “FIRE Number”) and how long it will last
|
|
||||||
until your chosen life expectancy.
|
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
<AccordionItem value="item-2">
|
<AccordionItem value="item-2">
|
||||||
<AccordionTrigger className="text-xl font-semibold">
|
<AccordionTrigger className="text-xl font-semibold">
|
||||||
Why isn't this just the 4% rule?
|
Is the Expected Growth Rate realistic? Finding the right
|
||||||
|
investing advice often starts here.
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="text-lg leading-relaxed">
|
<AccordionContent className="text-lg leading-relaxed">
|
||||||
The 4% rule is a useful starting point (25× annual spending),
|
Historically, diversified stock market investments have returned
|
||||||
but it assumes a fixed withdrawal rate with inflation
|
around 7-10% annually long-term (before inflation). A rate of 7%
|
||||||
adjustments and doesn't model ongoing savings or dynamic
|
(after fees) is common, but remember past performance
|
||||||
market returns. Our calculator simulates each year's
|
doesn't guarantee future results, a fundamental piece of
|
||||||
growth, contributions, and inflation-indexed withdrawals to give
|
investing advice. Choose a rate reflecting your risk tolerance
|
||||||
you a tailored picture.
|
and investment strategy.
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
<AccordionItem value="item-3">
|
<AccordionItem value="item-3">
|
||||||
<AccordionTrigger className="text-xl font-semibold">
|
<AccordionTrigger className="text-xl font-semibold">
|
||||||
How do I choose a realistic growth rate?
|
How does inflation impact my FIRE number?
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="text-lg leading-relaxed">
|
<AccordionContent className="text-lg leading-relaxed">
|
||||||
Historically, a diversified portfolio of equities and bonds has
|
Inflation erodes the purchasing power of money over time. Your
|
||||||
returned around 7-10% per year before inflation. We recommend
|
desired monthly allowance needs to increase each year just to
|
||||||
starting around 6-8% (net of fees), then running “what-if”
|
maintain the same standard of living. This calculator accounts
|
||||||
scenarios—5% on the conservative side, 10% on the aggressive
|
for this by adjusting your target allowance upwards based on the
|
||||||
side—to see how they affect your timeline.
|
inflation rate you provide, ensuring the calculated FIRE number
|
||||||
|
supports your desired lifestyle in future dollars.
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
<AccordionItem value="item-4">
|
<AccordionItem value="item-4">
|
||||||
<AccordionTrigger className="text-xl font-semibold">
|
<AccordionTrigger className="text-xl font-semibold">
|
||||||
How does inflation factor into my FIRE Number?
|
Can I really retire early with FIRE?
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="text-lg leading-relaxed">
|
<AccordionContent className="text-lg leading-relaxed">
|
||||||
Cost of living rises. To maintain today's lifestyle, your
|
Retiring significantly early is achievable but demands
|
||||||
monthly allowance must grow each year by your inflation rate.
|
discipline, a high savings rate, and smart investing. Success
|
||||||
This calculator automatically inflates your desired monthly
|
depends on income, expenses, savings habits, and investment
|
||||||
spending and subtracts it from your portfolio during retirement,
|
returns. Use this FIRE calculator as a planning tool,
|
||||||
ensuring your FIRE Number keeps pace with rising expenses.
|
understanding it provides estimates based on your assumptions
|
||||||
|
and chosen investing approach.
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
<AccordionItem value="item-5">
|
<AccordionItem value="item-5">
|
||||||
<AccordionTrigger className="text-xl font-semibold">
|
<AccordionTrigger className="text-xl font-semibold">
|
||||||
Can I really retire early with FIRE?
|
What does FIRE stand for?
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="text-lg leading-relaxed">
|
<AccordionContent className="text-lg leading-relaxed">
|
||||||
Early retirement is achievable with disciplined saving, smart
|
FIRE stands for Financial Independence, Retire Early. It
|
||||||
investing, and realistic assumptions. This tool helps you set
|
represents a lifestyle movement aimed at maximizing your savings
|
||||||
targets, visualize outcomes, and adjust inputs—so you can build
|
rate through increased income and/or decreased expenses to
|
||||||
confidence in your plan and make informed trade-offs between
|
achieve financial independence and retire much earlier than
|
||||||
lifestyle, risk, and timeline.
|
traditional retirement age.
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
<AccordionItem value="item-6">
|
<AccordionItem value="item-6">
|
||||||
<AccordionTrigger className="text-xl font-semibold">
|
<AccordionTrigger className="text-xl font-semibold">
|
||||||
How should I use this calculator effectively?
|
How much should I save each month?
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="text-lg leading-relaxed">
|
<AccordionContent className="text-lg leading-relaxed">
|
||||||
<ul className="ml-6 list-disc space-y-1">
|
FIRE enthusiasts typically aim to save 50-70% of their income.
|
||||||
<li>
|
The more you can save, the faster you'll reach your FIRE
|
||||||
Start with your actual numbers (capital, savings, age).
|
goal. However, the right amount depends on your income,
|
||||||
</li>
|
lifestyle, and target retirement age. Use the calculator to
|
||||||
<li>
|
experiment with different monthly savings amounts to see their
|
||||||
Set conservative - mid - aggressive growth rates to bound
|
impact on your retirement timeline.
|
||||||
possibilities.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Slide your retirement age to explore “early” vs.
|
|
||||||
“traditional” scenarios.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Review the chart—especially the reference lines—to see when
|
|
||||||
you hit FI and how withdrawals impact your balance.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Experiment with higher savings rates or lower target
|
|
||||||
spending to accelerate your path.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@@ -246,58 +208,50 @@ export default function HomePage() {
|
|||||||
FIRE Journey & Investing Resources
|
FIRE Journey & Investing Resources
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mb-6 text-lg leading-relaxed">
|
<p className="mb-6 text-lg leading-relaxed">
|
||||||
Ready to deepen your knowledge and build a bullet-proof plan? Below
|
Ready to dive deeper into FIRE and solidify your investing strategy?
|
||||||
are some of our favorite blogs, books, tools, and communities for
|
Explore these valuable resources for financial independence planning
|
||||||
financial independence and smart investing.
|
and investing advice:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="bg-foreground my-8 rounded-md p-4 text-lg">
|
<div className="bg-secondary/20 my-8 rounded-md p-4 text-lg">
|
||||||
<p className="font-semibold">Getting Started with FIRE:</p>
|
<p className="font-semibold">Getting Started with FIRE:</p>
|
||||||
<ol className="ml-6 list-decimal space-y-1">
|
<ol className="ml-6 list-decimal space-y-1">
|
||||||
<li>
|
<li>
|
||||||
Run your first projection above to find your target FIRE Number.
|
Calculate your personal numbers using this FIRE calculator and
|
||||||
</li>
|
other tools.
|
||||||
<li>Identify areas to boost savings or reduce expenses.</li>
|
|
||||||
<li>
|
|
||||||
Study index-fund strategies and low-cost investing advice.
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Join{" "}
|
Seek sound investing advice and consider joining communities
|
||||||
<a
|
like r/Fire for support.
|
||||||
href="https://www.reddit.com/r/Fire/"
|
|
||||||
target="_blank"
|
|
||||||
className="text-primary hover:underline"
|
|
||||||
>
|
|
||||||
supportive communities like r/Fire
|
|
||||||
</a>{" "}
|
|
||||||
to learn from real journeys.
|
|
||||||
</li>
|
</li>
|
||||||
|
<li>Explore books and podcasts to deepen your understanding</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-8 md:grid-cols-2">
|
<div className="grid gap-8 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-3 text-xl font-semibold">Blogs & Websites</h3>
|
<h3 className="mb-3 text-xl font-semibold">
|
||||||
|
Blogs & Investing Websites
|
||||||
|
</h3>
|
||||||
<ul className="ml-6 list-disc space-y-2 text-lg">
|
<ul className="ml-6 list-disc space-y-2 text-lg">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.mrmoneymustache.com/"
|
href="https://www.mrmoneymustache.com/2012/01/13/the-shockingly-simple-math-behind-early-retirement/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-primary hover:underline"
|
className="text-primary hover:underline"
|
||||||
>
|
>
|
||||||
Mr. Money Mustache
|
Mr. Money Mustache - Simple Math Behind Early Retirement &
|
||||||
</a>{" "}
|
Investing
|
||||||
- Hardcore frugality & early retirement success stories.
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.playingwithfire.co/"
|
href="https://www.playingwithfire.co/resources"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-primary hover:underline"
|
className="text-primary hover:underline"
|
||||||
>
|
>
|
||||||
Playing With FIRE
|
Playing With FIRE - Comprehensive Resources
|
||||||
</a>{" "}
|
</a>
|
||||||
- Community resources & real-life case studies.
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
@@ -305,15 +259,16 @@ export default function HomePage() {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-primary hover:underline"
|
className="text-primary hover:underline"
|
||||||
>
|
>
|
||||||
r/Fire
|
r/Fire Reddit Community
|
||||||
</a>{" "}
|
</a>
|
||||||
- Active forum for questions, tips, and support.
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-3 text-xl font-semibold">Books & Podcasts</h3>
|
<h3 className="mb-3 text-xl font-semibold">
|
||||||
|
Books & Investment Learning
|
||||||
|
</h3>
|
||||||
<ul className="ml-6 list-disc space-y-2 text-lg">
|
<ul className="ml-6 list-disc space-y-2 text-lg">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
@@ -321,19 +276,18 @@ export default function HomePage() {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-primary hover:underline"
|
className="text-primary hover:underline"
|
||||||
>
|
>
|
||||||
Your Money or Your Life
|
Your Money or Your Life - Foundational FIRE & Investing
|
||||||
</a>{" "}
|
Principles
|
||||||
- The classic guide to aligning money with values.
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://podcasts.apple.com/us/podcast/biggerpockets-money-podcast/id1330225136"
|
href="https://www.playingwithfire.co/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-primary hover:underline"
|
className="text-primary hover:underline"
|
||||||
>
|
>
|
||||||
BiggerPockets Money Podcast
|
Playing With FIRE Documentary
|
||||||
</a>{" "}
|
</a>
|
||||||
- Interviews on FIRE strategies and wealth building.
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
@@ -341,39 +295,37 @@ export default function HomePage() {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-primary hover:underline"
|
className="text-primary hover:underline"
|
||||||
>
|
>
|
||||||
InvestingFIRE Calculator Demo
|
BiggerPockets Money Podcast - FIRE Calculators & Investing
|
||||||
</a>{" "}
|
Strategies
|
||||||
- Deep dive on how interactive projections can guide your
|
</a>
|
||||||
plan.
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-3 text-xl font-semibold">
|
<h3 className="mb-3 text-xl font-semibold">
|
||||||
Additional Calculators & Tools
|
Additional FIRE & Investing Calculators
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="ml-6 list-disc space-y-2 text-lg">
|
<ul className="ml-6 list-disc space-y-2 text-lg">
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://ghostfol.io"
|
|
||||||
target="_blank"
|
|
||||||
className="text-primary hover:underline"
|
|
||||||
>
|
|
||||||
Ghostfolio
|
|
||||||
</a>{" "}
|
|
||||||
- Wealth management application for individuals.
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://walletburst.com/tools/coast-fire-calculator/"
|
href="https://walletburst.com/tools/coast-fire-calculator/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-primary hover:underline"
|
className="text-primary hover:underline"
|
||||||
>
|
>
|
||||||
Coast FIRE Calculator
|
Coast FIRE Calculator - For those considering a partial
|
||||||
</a>{" "}
|
early retirement
|
||||||
- When you “max out” early contributions but let compounding
|
</a>
|
||||||
do the rest.
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://www.empower.com/retirement-calculator"
|
||||||
|
target="_blank"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
Empower Retirement Planner - Free portfolio analysis and net
|
||||||
|
worth tracking
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
@@ -381,16 +333,43 @@ export default function HomePage() {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-primary hover:underline"
|
className="text-primary hover:underline"
|
||||||
>
|
>
|
||||||
Compound Interest Calculator
|
CAGR Compound Interest Calculator - Understand Investment
|
||||||
</a>{" "}
|
Growth
|
||||||
- Explore the power of growth rates in isolation.
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 text-xl font-semibold">
|
||||||
|
Recent Investing & FIRE Articles
|
||||||
|
</h3>
|
||||||
|
<ul className="ml-6 list-disc space-y-2 text-lg">
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://www.businessinsider.com/retiring-tech-early-coast-fire-make-me-millionaire-2025-4"
|
||||||
|
target="_blank"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
Coast FIRE: Retiring in your 30s while becoming a
|
||||||
|
millionaire by 60
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://www.businessinsider.com/financial-independence-retire-early-saving-loneliness-retreat-bali-making-friends-2025-2"
|
||||||
|
target="_blank"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
The Social Side of FIRE: Finding Community in Financial
|
||||||
|
Independence
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +0,0 @@
|
|||||||
import type { MetadataRoute } from "next";
|
|
||||||
|
|
||||||
export default function robots(): MetadataRoute.Robots {
|
|
||||||
return {
|
|
||||||
rules: {
|
|
||||||
userAgent: "*",
|
|
||||||
allow: "/",
|
|
||||||
},
|
|
||||||
sitemap: "https://investingfire.com/sitemap.xml",
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
import { BASE_URL } from "@/lib/constants";
|
|
||||||
import { type MetadataRoute } from "next";
|
|
||||||
|
|
||||||
export default function sitemap(): MetadataRoute.Sitemap {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
url: BASE_URL,
|
|
||||||
lastModified: new Date(),
|
|
||||||
changeFrequency: "yearly",
|
|
||||||
priority: 1,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
export const BASE_URL = "https://investingfire.com/";
|
|
Reference in New Issue
Block a user