Compare commits
21 Commits
d3c8995cb6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dda8cedd9a | |||
| 30e5198b14 | |||
| 8f2443572f | |||
| 28cb553ed0 | |||
| 013aa41965 | |||
| 6bb37bd6c3 | |||
| b9b16bcbf3 | |||
| bb9d09c653 | |||
| bf2c8b4e1a | |||
| 001a7b5c35 | |||
| 85089cd187 | |||
| 4b67c1ab2c | |||
| b9a2228422 | |||
| dda92f3f80 | |||
| ec52cbc116 | |||
| 17a694d4b5 | |||
| dc9cf1c1f2 | |||
| cb4a4e2f06 | |||
| 6c09c22656 | |||
| a7a2fe39ca | |||
| 3dc79aa425 |
7
.dockerignore
Normal file
7
.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
README.md
|
||||||
|
.next
|
||||||
|
.git
|
||||||
69
Dockerfile
Normal file
69
Dockerfile
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# syntax=docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6
|
||||||
|
|
||||||
|
FROM node:24-alpine@sha256:7e0bd0460b26eb3854ea5b99b887a6a14d665d14cae694b78ae2936d14b2befb AS base
|
||||||
|
|
||||||
|
# Install dependencies only when needed
|
||||||
|
FROM base AS deps
|
||||||
|
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies based on the preferred package manager
|
||||||
|
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
|
||||||
|
RUN \
|
||||||
|
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||||
|
elif [ -f package-lock.json ]; then npm ci; \
|
||||||
|
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
|
||||||
|
else echo "Lockfile not found." && exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Rebuild the source code only when needed
|
||||||
|
FROM base AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Next.js collects completely anonymous telemetry data about general usage.
|
||||||
|
# Learn more here: https://nextjs.org/telemetry
|
||||||
|
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||||
|
# ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
if [ -f yarn.lock ]; then yarn run build; \
|
||||||
|
elif [ -f package-lock.json ]; then npm run build; \
|
||||||
|
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
|
||||||
|
else echo "Lockfile not found." && exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Production image, copy all the files and run next
|
||||||
|
FROM base AS runner
|
||||||
|
# wget needed for healthcheck
|
||||||
|
RUN apk add --no-cache wget
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
# Uncomment the following line in case you want to disable telemetry during runtime.
|
||||||
|
# ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
# Copy public files. "[c]" as workaround for conditional matching since the folder might not exist.
|
||||||
|
COPY --from=builder /app/publi[c] ./public
|
||||||
|
|
||||||
|
# Automatically leverage output traces to reduce image size
|
||||||
|
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ENV PORT=3000
|
||||||
|
|
||||||
|
# server.js is created by next build from the standalone output
|
||||||
|
# https://nextjs.org/docs/pages/api-reference/config/next-config-js/output
|
||||||
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
CMD ["node", "server.js"]
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
import './src/env.ts';
|
import './src/env.ts';
|
||||||
|
|
||||||
/** @type {import("next").NextConfig} */
|
/** @type {import("next").NextConfig} */
|
||||||
const config = {};
|
const config = {
|
||||||
|
output: 'standalone',
|
||||||
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
28
package.json
28
package.json
@@ -33,11 +33,11 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cssnano": "^7.1.2",
|
"cssnano": "^7.1.2",
|
||||||
"lucide-react": "^0.556.0",
|
"lucide-react": "^0.561.0",
|
||||||
"next": "16.0.7",
|
"next": "16.0.10",
|
||||||
"next-plausible": "^3.12.4",
|
"next-plausible": "^3.12.4",
|
||||||
"react": "19.2.1",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.1",
|
"react-dom": "19.2.3",
|
||||||
"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",
|
||||||
@@ -45,32 +45,32 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.57.0",
|
"@playwright/test": "1.57.0",
|
||||||
"@tailwindcss/postcss": "4.1.17",
|
"@tailwindcss/postcss": "4.1.18",
|
||||||
"@testing-library/dom": "10.4.1",
|
"@testing-library/dom": "10.4.1",
|
||||||
"@testing-library/jest-dom": "6.9.1",
|
"@testing-library/jest-dom": "6.9.1",
|
||||||
"@testing-library/react": "16.3.0",
|
"@testing-library/react": "16.3.0",
|
||||||
"@testing-library/user-event": "14.6.1",
|
"@testing-library/user-event": "14.6.1",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.3",
|
||||||
"@types/react": "19.2.7",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "19.2.3",
|
"@types/react-dom": "19.2.3",
|
||||||
"@vitejs/plugin-react": "5.1.1",
|
"@vitejs/plugin-react": "5.1.2",
|
||||||
"eslint": "9.39.1",
|
"eslint": "9.39.2",
|
||||||
"eslint-config-next": "16.0.7",
|
"eslint-config-next": "16.0.10",
|
||||||
"eslint-config-prettier": "10.1.8",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"jsdom": "27.2.0",
|
"jsdom": "27.3.0",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"prettier": "3.7.4",
|
"prettier": "3.7.4",
|
||||||
"prettier-plugin-tailwindcss": "0.7.2",
|
"prettier-plugin-tailwindcss": "0.7.2",
|
||||||
"tailwindcss": "4.1.17",
|
"tailwindcss": "4.1.18",
|
||||||
"tw-animate-css": "1.4.0",
|
"tw-animate-css": "1.4.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "8.48.1",
|
"typescript-eslint": "8.49.0",
|
||||||
"vitest": "4.0.13"
|
"vitest": "4.0.15"
|
||||||
},
|
},
|
||||||
"ct3aMetadata": {
|
"ct3aMetadata": {
|
||||||
"initVersion": "7.39.3"
|
"initVersion": "7.39.3"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.24.0",
|
"packageManager": "pnpm@10.25.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@types/react": "19.2.7",
|
"@types/react": "19.2.7",
|
||||||
|
|||||||
1401
pnpm-lock.yaml
generated
1401
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -333,9 +333,18 @@ export default function FireCalculatorForm({
|
|||||||
// Sort to find percentiles
|
// Sort to find percentiles
|
||||||
balancesForYear.sort((a, b) => a - b);
|
balancesForYear.sort((a, b) => a - b);
|
||||||
|
|
||||||
const p10 = balancesForYear[Math.floor(numSimulations * 0.4)];
|
const pickPercentile = (fraction: number) => {
|
||||||
const p50 = balancesForYear[Math.floor(numSimulations * 0.5)];
|
const clampedIndex = Math.min(
|
||||||
const p90 = balancesForYear[Math.floor(numSimulations * 0.6)];
|
balancesForYear.length - 1,
|
||||||
|
Math.max(0, Math.floor((balancesForYear.length - 1) * fraction)),
|
||||||
|
);
|
||||||
|
return balancesForYear[clampedIndex];
|
||||||
|
};
|
||||||
|
|
||||||
|
// For Monte Carlo, we present a narrow middle band (40th-60th) to show typical outcomes
|
||||||
|
const p10 = pickPercentile(0.4);
|
||||||
|
const p50 = pickPercentile(0.5);
|
||||||
|
const p90 = pickPercentile(0.6);
|
||||||
|
|
||||||
// Calculate other metrics (using deterministic logic for "untouched" etc for simplicity, or p50)
|
// Calculate other metrics (using deterministic logic for "untouched" etc for simplicity, or p50)
|
||||||
// We need to reconstruct the "standard" fields for compatibility with the chart
|
// We need to reconstruct the "standard" fields for compatibility with the chart
|
||||||
@@ -437,13 +446,23 @@ export default function FireCalculatorForm({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isMonteCarlo = form.watch('simulationMode') === 'monte-carlo';
|
const simulationModeValue = form.watch('simulationMode');
|
||||||
|
const isMonteCarlo = simulationModeValue === 'monte-carlo';
|
||||||
const chartData =
|
const chartData =
|
||||||
result?.yearlyData.map((row) => ({
|
result?.yearlyData.map((row) => ({
|
||||||
...row,
|
...row,
|
||||||
mcRange: (row.balanceP90 ?? 0) - (row.balanceP10 ?? 0),
|
mcRange: (row.balanceP90 ?? 0) - (row.balanceP10 ?? 0),
|
||||||
})) ?? [];
|
})) ?? [];
|
||||||
|
|
||||||
|
// Ensure we always have a fresh calculation when switching simulation modes (or on first render)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!result) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
form.handleSubmit(onSubmit)();
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [simulationModeValue]);
|
||||||
|
|
||||||
const projectionChartConfig: ChartConfig = {
|
const projectionChartConfig: ChartConfig = {
|
||||||
year: {
|
year: {
|
||||||
label: 'Year',
|
label: 'Year',
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ describe('FireCalculatorForm', () => {
|
|||||||
await screen.findByText('Financial Projection');
|
await screen.findByText('Financial Projection');
|
||||||
const bandLegend = await screen.findByTestId('mc-band-legend');
|
const bandLegend = await screen.findByTestId('mc-band-legend');
|
||||||
|
|
||||||
expect(bandLegend).toHaveTextContent('10th-90th percentile');
|
expect(bandLegend).toHaveTextContent('40th-60th percentile');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles withdrawal strategy selection', async () => {
|
it('handles withdrawal strategy selection', async () => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|||||||
import { CoastFireChart } from '@/app/components/charts/CoastFireChart';
|
import { CoastFireChart } from '@/app/components/charts/CoastFireChart';
|
||||||
import { AuthorBio } from '@/app/components/AuthorBio';
|
import { AuthorBio } from '@/app/components/AuthorBio';
|
||||||
import { FaqSection, type FaqItem } from '@/app/components/FaqSection';
|
import { FaqSection, type FaqItem } from '@/app/components/FaqSection';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
const faqs: FaqItem[] = [
|
const faqs: FaqItem[] = [
|
||||||
{
|
{
|
||||||
@@ -39,16 +40,28 @@ const faqs: FaqItem[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata: Metadata = {
|
||||||
title: `Coast FIRE vs. Lean FIRE: Which Strategy Is Right For You? (${new Date().getFullYear().toString()})`,
|
title: `Coast FIRE vs. Lean FIRE: Which Strategy Is Right For You? (${new Date().getFullYear().toString()})`,
|
||||||
description:
|
description:
|
||||||
'Compare Coast FIRE (front-loading savings) with Lean FIRE (minimalist living). See the math, pros, cons, and find your path to freedom.',
|
'Compare Coast FIRE (front-loading savings) with Lean FIRE (minimalist living). See the math, pros, cons, and find your path to freedom.',
|
||||||
|
alternates: {
|
||||||
|
canonical: 'https://investingfire.com/learn/coast-fire-vs-lean-fire',
|
||||||
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Coast FIRE vs. Lean FIRE: The Ultimate Comparison',
|
title: 'Coast FIRE vs. Lean FIRE: The Ultimate Comparison',
|
||||||
description:
|
description:
|
||||||
"Don't just retire early—retire smarter. We break down the two most popular alternative FIRE strategies.",
|
"Don't just retire early—retire smarter. We break down the two most popular alternative FIRE strategies.",
|
||||||
type: 'article',
|
type: 'article',
|
||||||
|
siteName: 'InvestingFIRE',
|
||||||
url: 'https://investingfire.com/learn/coast-fire-vs-lean-fire',
|
url: 'https://investingfire.com/learn/coast-fire-vs-lean-fire',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://investingfire.com/apple-icon.png',
|
||||||
|
width: 180,
|
||||||
|
height: 180,
|
||||||
|
alt: 'InvestingFIRE Logo',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Info } from 'lucide-react';
|
import { Info } from 'lucide-react';
|
||||||
import { AuthorBio } from '@/app/components/AuthorBio';
|
import { AuthorBio } from '@/app/components/AuthorBio';
|
||||||
import { FaqSection, type FaqItem } from '@/app/components/FaqSection';
|
import { FaqSection, type FaqItem } from '@/app/components/FaqSection';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
const faqs: FaqItem[] = [
|
const faqs: FaqItem[] = [
|
||||||
{
|
{
|
||||||
@@ -33,15 +34,27 @@ const faqs: FaqItem[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Home Bias in Investing: Why It Matters and How to Fix It',
|
title: 'Home Bias in Investing: Why It Matters and How to Fix It',
|
||||||
description:
|
description:
|
||||||
'Home bias concentrates risk in one country. Learn why it happens, how it hurts returns, and simple steps to global diversification.',
|
'Home bias concentrates risk in one country. Learn why it happens, how it hurts returns, and simple steps to global diversification.',
|
||||||
|
alternates: {
|
||||||
|
canonical: 'https://investingfire.com/learn/home-bias-in-investing',
|
||||||
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Home Bias in Investing: Why It Matters and How to Fix It',
|
title: 'Home Bias in Investing: Why It Matters and How to Fix It',
|
||||||
description: 'Reduce country concentration, improve diversification, and stay tax aware.',
|
description: 'Reduce country concentration, improve diversification, and stay tax aware.',
|
||||||
type: 'article',
|
type: 'article',
|
||||||
|
siteName: 'InvestingFIRE',
|
||||||
url: 'https://investingfire.com/learn/home-bias-in-investing',
|
url: 'https://investingfire.com/learn/home-bias-in-investing',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://investingfire.com/apple-icon.png',
|
||||||
|
width: 180,
|
||||||
|
height: 180,
|
||||||
|
alt: 'InvestingFIRE Logo',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,30 @@ import Link from 'next/link';
|
|||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import BlurThing from '../components/blur-thing';
|
import BlurThing from '../components/blur-thing';
|
||||||
import { RETIRE_AT_AGE_PRESETS } from '@/lib/retire-at';
|
import { RETIRE_AT_AGE_PRESETS } from '@/lib/retire-at';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Learn FIRE | Financial Independence Guides & Resources',
|
title: 'Learn FIRE | Financial Independence Guides & Resources',
|
||||||
description:
|
description:
|
||||||
'Master the art of Financial Independence and Early Retirement. Deep dives into safe withdrawal rates, asset allocation, and FIRE strategies.',
|
'Master the art of Financial Independence and Early Retirement. Deep dives into safe withdrawal rates, asset allocation, and FIRE strategies.',
|
||||||
|
alternates: {
|
||||||
|
canonical: 'https://investingfire.com/learn',
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title: 'Learn FIRE | Financial Independence Guides & Resources',
|
||||||
|
description:
|
||||||
|
'Master the art of Financial Independence and Early Retirement. Deep dives into safe withdrawal rates, asset allocation, and FIRE strategies.',
|
||||||
|
siteName: 'InvestingFIRE',
|
||||||
|
url: 'https://investingfire.com/learn',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://investingfire.com/apple-icon.png',
|
||||||
|
width: 180,
|
||||||
|
height: 180,
|
||||||
|
alt: 'InvestingFIRE Logo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const retireAgeLinks = RETIRE_AT_AGE_PRESETS;
|
const retireAgeLinks = RETIRE_AT_AGE_PRESETS;
|
||||||
|
|||||||
@@ -76,7 +76,16 @@ export const generateMetadata = async ({ params }: RetireAtPageProps): Promise<M
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
url: canonical,
|
url: canonical,
|
||||||
|
siteName: 'InvestingFIRE',
|
||||||
type: 'article',
|
type: 'article',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://investingfire.com/apple-icon.png',
|
||||||
|
width: 180,
|
||||||
|
height: 180,
|
||||||
|
alt: 'InvestingFIRE Logo',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Info } from 'lucide-react';
|
|||||||
import { FourPercentRuleChart } from '@/app/components/charts/FourPercentRuleChart';
|
import { FourPercentRuleChart } from '@/app/components/charts/FourPercentRuleChart';
|
||||||
import { AuthorBio } from '@/app/components/AuthorBio';
|
import { AuthorBio } from '@/app/components/AuthorBio';
|
||||||
import { FaqSection, type FaqItem } from '@/app/components/FaqSection';
|
import { FaqSection, type FaqItem } from '@/app/components/FaqSection';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
const faqs: FaqItem[] = [
|
const faqs: FaqItem[] = [
|
||||||
{
|
{
|
||||||
@@ -39,14 +40,26 @@ const faqs: FaqItem[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Safe Withdrawal Rates & The 4% Rule Explained (2025 Update)',
|
title: 'Safe Withdrawal Rates & The 4% Rule Explained (2025 Update)',
|
||||||
description: `Is the 4% rule safe in ${new Date().getFullYear().toString()}? We analyze the Trinity Study, sequence of returns risk, and variable withdrawal strategies for a bulletproof retirement.`,
|
description: `Is the 4% rule safe in ${new Date().getFullYear().toString()}? We analyze the Trinity Study, sequence of returns risk, and variable withdrawal strategies for a bulletproof retirement.`,
|
||||||
|
alternates: {
|
||||||
|
canonical: 'https://investingfire.com/learn/safe-withdrawal-rate-4-percent-rule',
|
||||||
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Safe Withdrawal Rates & The 4% Rule Explained',
|
title: 'Safe Withdrawal Rates & The 4% Rule Explained',
|
||||||
description: "Don't run out of money. Understanding the math behind safe retirement withdrawals.",
|
description: "Don't run out of money. Understanding the math behind safe retirement withdrawals.",
|
||||||
type: 'article',
|
type: 'article',
|
||||||
|
siteName: 'InvestingFIRE',
|
||||||
url: 'https://investingfire.com/learn/safe-withdrawal-rate-4-percent-rule',
|
url: 'https://investingfire.com/learn/safe-withdrawal-rate-4-percent-rule',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://investingfire.com/apple-icon.png',
|
||||||
|
width: 180,
|
||||||
|
height: 180,
|
||||||
|
alt: 'InvestingFIRE Logo',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { FireFlowchart } from '@/app/components/charts/FireFlowchart';
|
import { FireFlowchart } from '@/app/components/charts/FireFlowchart';
|
||||||
import { AuthorBio } from '@/app/components/AuthorBio';
|
import { AuthorBio } from '@/app/components/AuthorBio';
|
||||||
import { FaqSection, type FaqItem } from '@/app/components/FaqSection';
|
import { FaqSection, type FaqItem } from '@/app/components/FaqSection';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
const faqs: FaqItem[] = [
|
const faqs: FaqItem[] = [
|
||||||
{
|
{
|
||||||
@@ -37,15 +38,27 @@ const faqs: FaqItem[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata: Metadata = {
|
||||||
title: `What is FIRE? The Ultimate Guide to Financial Independence (${new Date().getFullYear().toString()})`,
|
title: `What is FIRE? The Ultimate Guide to Financial Independence (${new Date().getFullYear().toString()})`,
|
||||||
description:
|
description:
|
||||||
'Discover the FIRE movement (Financial Independence, Retire Early). Learn how to calculate your FIRE number, savings rate, and retire decades ahead of schedule.',
|
'Discover the FIRE movement (Financial Independence, Retire Early). Learn how to calculate your FIRE number, savings rate, and retire decades ahead of schedule.',
|
||||||
|
alternates: {
|
||||||
|
canonical: 'https://investingfire.com/learn/what-is-fire',
|
||||||
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'What is FIRE? The Ultimate Guide to Financial Independence',
|
title: 'What is FIRE? The Ultimate Guide to Financial Independence',
|
||||||
description: 'Stop trading time for money. The comprehensive guide to regaining your freedom.',
|
description: 'Stop trading time for money. The comprehensive guide to regaining your freedom.',
|
||||||
type: 'article',
|
type: 'article',
|
||||||
|
siteName: 'InvestingFIRE',
|
||||||
url: 'https://investingfire.com/learn/what-is-fire',
|
url: 'https://investingfire.com/learn/what-is-fire',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://investingfire.com/apple-icon.png',
|
||||||
|
width: 180,
|
||||||
|
height: 180,
|
||||||
|
alt: 'InvestingFIRE Logo',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Info } from 'lucide-react';
|
import { Info } from 'lucide-react';
|
||||||
import { AuthorBio } from '@/app/components/AuthorBio';
|
import { AuthorBio } from '@/app/components/AuthorBio';
|
||||||
import { FaqSection, type FaqItem } from '@/app/components/FaqSection';
|
import { FaqSection, type FaqItem } from '@/app/components/FaqSection';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
const faqs: FaqItem[] = [
|
const faqs: FaqItem[] = [
|
||||||
{
|
{
|
||||||
@@ -34,15 +35,27 @@ const faqs: FaqItem[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata: Metadata = {
|
||||||
title: `Where to Park Your Money for FIRE (${new Date().getFullYear().toString()})`,
|
title: `Where to Park Your Money for FIRE (${new Date().getFullYear().toString()})`,
|
||||||
description:
|
description:
|
||||||
'Build a globally diversified, low-cost index portfolio, avoid home bias, and use the right tax wrappers—wherever you live. A practical guide for FIRE investors.',
|
'Build a globally diversified, low-cost index portfolio, avoid home bias, and use the right tax wrappers—wherever you live. A practical guide for FIRE investors.',
|
||||||
|
alternates: {
|
||||||
|
canonical: 'https://investingfire.com/learn/where-to-park-your-money',
|
||||||
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Where to Park Your Money for FIRE',
|
title: 'Where to Park Your Money for FIRE',
|
||||||
description: 'Global index investing playbook: avoid home bias, cut fees, optimize taxes.',
|
description: 'Global index investing playbook: avoid home bias, cut fees, optimize taxes.',
|
||||||
type: 'article',
|
type: 'article',
|
||||||
|
siteName: 'InvestingFIRE',
|
||||||
url: 'https://investingfire.com/learn/where-to-park-your-money',
|
url: 'https://investingfire.com/learn/where-to-park-your-money',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://investingfire.com/apple-icon.png',
|
||||||
|
width: 180,
|
||||||
|
height: 180,
|
||||||
|
alt: 'InvestingFIRE Logo',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import FireCalculatorForm from './components/FireCalculatorForm';
|
|||||||
import BackgroundPattern from './components/BackgroundPattern';
|
import BackgroundPattern from './components/BackgroundPattern';
|
||||||
import { FaqSection, type FaqItem } from './components/FaqSection';
|
import { FaqSection, type FaqItem } from './components/FaqSection';
|
||||||
import { Testimonials } from './components/Testimonials';
|
import { Testimonials } from './components/Testimonials';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
const faqs: FaqItem[] = [
|
const faqs: FaqItem[] = [
|
||||||
{
|
{
|
||||||
@@ -38,6 +39,31 @@ const faqs: FaqItem[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: `InvestingFIRE | Finance and Retirement Calculator ${new Date().getFullYear().toString()}`,
|
||||||
|
description:
|
||||||
|
'Achieve Financial Independence & Early Retirement! Plan your FIRE journey with the InvestingFIRE calculator and get personal projections in gorgeous graphs..',
|
||||||
|
alternates: {
|
||||||
|
canonical: 'https://investingfire.com',
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title: `InvestingFIRE | Finance and Retirement Calculator ${new Date().getFullYear().toString()}`,
|
||||||
|
description:
|
||||||
|
'Achieve Financial Independence & Early Retirement! Plan your FIRE journey with the InvestingFIRE calculator and get personal projections in gorgeous graphs.',
|
||||||
|
type: 'website',
|
||||||
|
url: 'https://investingfire.com',
|
||||||
|
siteName: 'InvestingFIRE',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://investingfire.com/apple-icon.png',
|
||||||
|
width: 180,
|
||||||
|
height: 180,
|
||||||
|
alt: 'InvestingFIRE Logo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
<div className="from-background via-primary/10 to-secondary/10 text-foreground relative flex min-h-screen w-full flex-col items-center overflow-hidden bg-gradient-to-b px-4 pt-6 pb-16">
|
<div className="from-background via-primary/10 to-secondary/10 text-foreground relative flex min-h-screen w-full flex-col items-center overflow-hidden bg-gradient-to-b px-4 pt-6 pb-16">
|
||||||
|
|||||||
Reference in New Issue
Block a user