Compare commits

..

1 Commits

Author SHA1 Message Date
c5e912d4ed chore(deps): update dependency react-hook-form to v7.68.0
All checks were successful
Lint / Lint and Typecheck (push) Successful in 45s
Lint / Lint and Typecheck (pull_request) Successful in 40s
2025-12-07 01:46:01 +01:00
13 changed files with 739 additions and 927 deletions

View File

@@ -1,7 +0,0 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git

View File

@@ -1,69 +0,0 @@
# 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"]

View File

@@ -5,8 +5,6 @@
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;

View File

@@ -33,44 +33,44 @@
"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.561.0", "lucide-react": "^0.556.0",
"next": "16.0.10", "next": "16.0.7",
"next-plausible": "^3.12.4", "next-plausible": "^3.12.4",
"react": "19.2.3", "react": "19.2.1",
"react-dom": "19.2.3", "react-dom": "19.2.1",
"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": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "1.57.0", "@playwright/test": "^1.57.0",
"@tailwindcss/postcss": "4.1.18", "@tailwindcss/postcss": "4.1.17",
"@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.3", "@types/node": "24.10.1",
"@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.2", "@vitejs/plugin-react": "^5.1.1",
"eslint": "9.39.2", "eslint": "9.39.1",
"eslint-config-next": "16.0.10", "eslint-config-next": "16.0.7",
"eslint-config-prettier": "10.1.8", "eslint-config-prettier": "10.1.8",
"jsdom": "27.3.0", "jsdom": "^27.2.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.18", "tailwindcss": "4.1.17",
"tw-animate-css": "1.4.0", "tw-animate-css": "1.4.0",
"typescript": "5.9.3", "typescript": "5.9.3",
"typescript-eslint": "8.49.0", "typescript-eslint": "8.48.1",
"vitest": "4.0.15" "vitest": "^4.0.13"
}, },
"ct3aMetadata": { "ct3aMetadata": {
"initVersion": "7.39.3" "initVersion": "7.39.3"
}, },
"packageManager": "pnpm@10.25.0", "packageManager": "pnpm@10.24.0",
"pnpm": { "pnpm": {
"overrides": { "overrides": {
"@types/react": "19.2.7", "@types/react": "19.2.7",

1417
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,6 @@ 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[] = [
{ {
@@ -40,28 +39,16 @@ const faqs: FaqItem[] = [
}, },
]; ];
export const metadata: Metadata = { export const 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',
},
],
}, },
}; };

View File

@@ -4,7 +4,6 @@ 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,27 +33,15 @@ const faqs: FaqItem[] = [
}, },
]; ];
export const metadata: Metadata = { export const 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',
},
],
}, },
}; };

View File

@@ -2,30 +2,11 @@ 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: Metadata = { export const 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;

View File

@@ -76,16 +76,7 @@ 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',
},
],
}, },
}; };
}; };

View File

@@ -5,7 +5,6 @@ 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[] = [
{ {
@@ -40,26 +39,14 @@ const faqs: FaqItem[] = [
}, },
]; ];
export const metadata: Metadata = { export const 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',
},
],
}, },
}; };

View File

@@ -3,7 +3,6 @@ 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[] = [
{ {
@@ -38,27 +37,15 @@ const faqs: FaqItem[] = [
}, },
]; ];
export const metadata: Metadata = { export const 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',
},
],
}, },
}; };

View File

@@ -5,7 +5,6 @@ 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[] = [
{ {
@@ -35,27 +34,15 @@ const faqs: FaqItem[] = [
}, },
]; ];
export const metadata: Metadata = { export const 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',
},
],
}, },
}; };

View File

@@ -4,7 +4,6 @@ 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[] = [
{ {
@@ -39,31 +38,6 @@ 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">