Mrinal Jain
← Back to all talks

April 9, 2026

Upcoming / ScheduledFeatured

REadme File

Passionate about bridging engineering with education to empower teams and drive product impact.

View Event Details
blogfalanadhimaka
REadme File

Netlify Status

Passionate about bridging engineering with education to empower teams and drive product impact.

Blog Implementation

A markdown-powered personal blog has been integrated in this project inspired by https://github.com/machadop1407/nextjs-markdown-blog and upgraded to modern Next.js App Router patterns.

Routes

  • /blog: blog listing page
  • /blog/[slug]: blog details page
  • /post: alias route to the same listing UI and data
  • /post/[slug]: alias route to the same details UI and data

Where Content Lives

Create markdown files in:

  • src/content/blog

Each file name becomes the slug. Example:

  • src/content/blog/my-awesome-post.md -> /blog/my-awesome-post

Markdown Frontmatter

Each markdown file supports:

---
title: "My First Post"
date: "2026-03-31"
excerpt: "A short summary for card previews"
tags:
  - nextjs
  - markdown
---

# Heading

Your markdown content here.

Architecture

  • src/lib/blog.ts: Server-side markdown loader, frontmatter parsing, sorting, and HTML transform
  • src/types/blog.ts: Blog domain types
  • src/components/blog/BlogList.tsx: Shared listing UI
  • src/components/blog/BlogPostView.tsx: Shared article UI
  • src/app/blog/page.tsx: Blog index route
  • src/app/blog/[slug]/page.tsx: Blog article route with static params + metadata
  • src/app/post/page.tsx and src/app/post/[slug]/page.tsx: Route aliases that reuse blog routes

Key Next.js Features Used

  • App Router route segments in src/app
  • generateStaticParams for pre-rendering blog posts
  • generateMetadata for per-post SEO metadata
  • Server Components for file-system markdown loading

Local Development

npm install
npm run dev

Then visit:

Adding New Posts

  1. Add a new .md file under src/content/blog.
  2. Fill frontmatter and markdown body.
  3. Restart dev server only if file watchers are stale; usually hot reload picks it up.
  4. Open /blog to verify ordering and rendering. Passionate about bridging engineering with education to empower teams and drive product impact.

Theme System (Light, Dark, System)

This project now supports 3 theme modes across the website:

  • light
  • dark
  • system (matches the OS/browser preference)

Theme state is handled with next-themes, persisted in local storage, and applied with a class-based strategy (class="dark") for Tailwind CSS v4.

How It Works

  1. ThemeProvider wraps the app in src/app/layout.tsx.
  2. Tailwind dark variant is enabled in src/app/globals.css using:
@custom-variant dark (&:where(.dark, .dark *));
  1. CSS tokens switch by class:
:root {
	--background: #ffffff;
	--foreground: #171717;
}

.dark {
	--background: #0a0a0a;
	--foreground: #ededed;
}
  1. ThemeToggle in the header lets users select light, dark, or system dynamically.

How To Write Theme-Safe Classes (For Future Work)

Use Tailwind utility pairs where needed:

  • Backgrounds: bg-white dark:bg-slate-900
  • Text: text-slate-900 dark:text-slate-100
  • Borders: border-slate-200 dark:border-slate-700
  • Secondary text: text-slate-600 dark:text-slate-300

Examples:

<div className="rounded-xl bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700">
	<h2 className="text-slate-900 dark:text-slate-100">Title</h2>
	<p className="text-slate-600 dark:text-slate-300">Description</p>
</div>
<button className="bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400">
	Action
</button>

Rules To Keep Theme Support Stable

  • Always provide a dark: counterpart for non-brand neutral colors.
  • Prefer semantic color families (slate, neutral) for text/surface in content-heavy areas.
  • Keep brand CTA colors consistent unless there is a contrast issue.
  • For reusable sections with many custom colors (like resume), use CSS variables and override them under .dark.
  • Avoid relying only on prefers-color-scheme; user choice from toggle should take priority.

Files Added/Updated For Theming

  • src/components/ThemeProvider.tsx
  • src/components/ThemeToggle.tsx
  • src/app/layout.tsx
  • src/app/globals.css
  • Shared page/component styles updated with dark: variants
  • Resume styles converted to variable-based theme tokens in src/components/resume/Resume.css

References Used

Linting

This project uses the ESLint CLI with a Next.js 16 flat config.

  • npm run lint runs ESLint across the project.
  • npm run lint:fix applies safe autofixes.
  • npm run lint:strict fails on any warning.

The config includes:

  • eslint-config-next/core-web-vitals
  • eslint-config-next/typescript
  • the recommended rule sets from eslint-plugin-react, eslint-plugin-react-hooks, and @next/eslint-plugin-next
  • eslint-config-prettier/flat to avoid formatter conflicts

Contact Form Integrations

The contact form on /contact submits inquiries to two destinations:

  • Google Sheet via webhook URL (for spreadsheet logging)
  • Email delivery to jain.mrinal140@gmail.com

Preferred email path (recommended when local or cloud network blocks SMTP):

  • Resend HTTPS API

Fallback email path:

  • SMTP (Gmail or other provider)

Option A: Resend (recommended)

Set these:

  • RESEND_API_KEY: API key from Resend
  • RESEND_FROM_EMAIL: verified sender in Resend (example: Contact <onboarding@resend.dev> for testing)

When both variables are present, the API uses Resend and skips SMTP.

Option B: SMTP

Set these environment variables in .env.local and production:

  • CONTACT_SHEET_WEBHOOK_URL: your Google Apps Script (or other webhook) endpoint for writing rows
  • CONTACT_SMTP_HOST: SMTP host (example: smtp.gmail.com)
  • CONTACT_SMTP_PORT: SMTP port (example: 587 or 465)
  • CONTACT_SMTP_FAMILY: optional IP family, 4 (default) or 6
  • CONTACT_SMTP_USER: SMTP username/login
  • CONTACT_SMTP_PASS: SMTP password or app password
  • CONTACT_FROM_EMAIL: optional from-address shown on outgoing emails (defaults to CONTACT_SMTP_USER)

If either sheet logging or email delivery fails, the API returns an error and the form shows an error message.

IndexNow Setup

This project includes a script to submit URLs from your sitemap to IndexNow.

1) Add environment variables

Copy values from .env.example into your local .env.local (and production env on your host):

  • NEXT_PUBLIC_SITE_URL
  • INDEXNOW_KEY
  • INDEXNOW_KEY_LOCATION (optional)
  • INDEXNOW_SITEMAP_URL (optional)
  • INDEXNOW_ENDPOINT (optional)

2) Prove site ownership (required by IndexNow)

Create a text file in public/ named exactly {INDEXNOW_KEY}.txt. The file content must be only your key.

Example:

  • file path: public/f34f184d10c049ef99aa7637cdc4ef04.txt
  • file content: f34f184d10c049ef99aa7637cdc4ef04

After deploy, verify:

  • https://<your-domain>/<INDEXNOW_KEY>.txt returns the key text.

3) Submit URLs

Submit all URLs from sitemap:

npm run indexnow:submit:all

Submit URLs changed after a date:

npm run indexnow:submit -- --since=2026-01-01

Submit one URL:

npm run indexnow:submit -- --url=https://<your-domain>/post/example

Dry run (prepare URLs only, no API call):

npm run indexnow:submit -- --all --dry-run

Notes:

  • IndexNow accepts up to 10,000 URLs per request. The script auto-batches.
  • Non-200/202 responses fail the command so you can retry or debug quickly.

4) GitHub Actions automation (optional)

Workflow file:

  • .github/workflows/indexnow-submit.yml

Add these GitHub Actions repository secrets:

  • NEXT_PUBLIC_SITE_URL (required)
  • INDEXNOW_KEY (required)
  • INDEXNOW_KEY_LOCATION (optional)
  • INDEXNOW_SITEMAP_URL (optional)
  • INDEXNOW_ENDPOINT (optional)

What it does:

  • Runs daily on a schedule (UTC).
  • Supports manual runs with all, since, or url mode.
  • Verifies sitemap and key file are reachable before submitting.

Manual run from GitHub UI:

  1. Open Actions -> IndexNow Submit -> Run workflow.
  2. Choose all to submit all sitemap URLs.
  3. Choose since to submit URLs changed after a date (YYYY-MM-DD).
  4. Choose url to submit one URL.
  5. Optionally enable dry_run to validate without calling IndexNow.