A cutting-edge, production-ready portfolio website built with Next.js 15, React 18, TypeScript, TailwindCSS, and Framer Motion. This project showcases modern web development practices, including server-side rendering, API routes, email functionality, analytics integration, internationalization (i18n), and stunning animations.
- Live Demo: https://www.arnobmahmud.com/
Author: Arnob Mahmud | License: MIT
π Open Source Project - This is an open-source project. Feel free to use, enhance, and extend this project to the next level! Contributions, improvements, forks, and stars are always welcome. Together, we can make this portfolio template even better!
- Overview
- Features
- Technology Stack
- Project Structure
- Installation & Setup
- Environment Variables
- How to Run
- Internationalization (i18n)
- Components Overview
- API Endpoints
- Pages & Routes
- Custom Hooks
- Styling & Animations
- Email Configuration
- Analytics Integration
- SEO & Metadata
- Deployment
- Reusable Components Guide
- Best Practices
- Keywords & SEO
- Troubleshooting
- Contributing
- License
- Conclusion
This is a production-ready, modern portfolio website that demonstrates best practices in Next.js development. It features a complete full-stack implementation with server-side rendering, API routes, email functionality, multi-language support, and professional animations.
- Next.js 15 with App Router and Server Components
- TypeScript for type safety throughout the codebase
- Internationalization (i18n) - English and German support
- Email Integration - Contact form with auto-reply functionality
- Analytics - Google Analytics 4 and Vercel Analytics
- SEO Optimized - Meta tags, Open Graph, structured data, sitemap
- Responsive Design - Mobile-first approach with TailwindCSS
- Smooth Animations - Framer Motion for page transitions and interactions
- Modern UI Components - Radix UI and Shadcn UI components
- Performance Optimized - Image optimization, lazy loading, code splitting
- β‘ Next.js 15 with App Router and Server Components
- π¨ Modern UI/UX with TailwindCSS and Shadcn UI components
- π Smooth Animations powered by Framer Motion
- π± Fully Responsive design for all devices
- π Internationalization - English and German language support
- π Type-Safe with TypeScript
- π§ Contact Form with email notifications and auto-reply
- π Analytics with Google Analytics & Vercel Analytics
- π― SEO Optimized with meta tags, Open Graph, and structured data
- βΏ Accessible components following WCAG guidelines
- π Fast Performance with optimized images and lazy loading
- π€ AI Chatbot Widget integration for interactive FAQ
- πͺ Typewriter Effect on homepage hero section
- π Project Carousel with Swiper.js
- π Grid/List View Toggle for projects showcase
- π Animated Counter for statistics
- π¬ Page Transitions with smooth animations
- πͺ Stair Transition Effect between pages
- π Scroll-to-Top button functionality
- π― Custom Tooltips for enhanced UX
- π± Mobile Navigation with hamburger menu
- π Loading States for async operations
- π Language Switcher with cookie persistence
- π Dynamic Sitemap generation
- Framework: Next.js 15.5.9 (React 18.3.1)
- Language: TypeScript 5.7.2
- Styling: TailwindCSS 3.4.17
- Animations: Framer Motion 12.23.24
- UI Components: Radix UI, Shadcn UI
- Icons: React Icons 5.5.0, Lucide React 0.546.0
- Carousel: Swiper 12.0.2
- Internationalization: i18next 25.8.0, react-i18next 16.5.3
- Runtime: Node.js
- Email Service: Nodemailer 7.0.9
- HTTP Client: Axios 1.12.2
- Email Templates: @react-email/render 1.4.0
- Alternative Email: Resend 6.2.0
- Web Analytics: Google Analytics 4
- Performance: Vercel Analytics 1.5.0
- Package Manager: npm/yarn/pnpm
- Linting: ESLint 8.57.0
- Build Tool: Turbopack (Next.js 15)
- Deployment: Vercel
portfolio-arnob-new/
βββ app/ # Next.js App Router
β βββ api/ # API Routes
β β βββ send-email/ # Main contact form handler
β β β βββ route.ts
β β βββ send-auto-reply/ # Auto-reply email handler
β β βββ route.ts
β βββ about/ # About page
β β βββ page.tsx
β βββ contact/ # Contact page
β β βββ page.tsx
β βββ faq/ # FAQ page
β β βββ page.tsx
β βββ privacy/ # Privacy policy page
β β βββ page.tsx
β βββ resume/ # Resume/CV page
β β βββ page.tsx
β βββ services/ # Services offered page
β β βββ page.tsx
β βββ terms/ # Terms of service page
β β βββ page.tsx
β βββ work/ # Portfolio/Projects page
β β βββ page.tsx
β βββ globals.css # Global styles & animations
β βββ layout.tsx # Root layout with metadata
β βββ page.tsx # Homepage
β βββ sitemap.ts # Dynamic sitemap generation
β
βββ components/ # React Components
β βββ pages/ # Page-specific components
β β βββ AboutPage.tsx # About page content
β β βββ ContactPage.tsx # Contact form with validation
β β βββ FAQPage.tsx # FAQ accordion
β β βββ HomePage.tsx # Hero section with typewriter
β β βββ PrivacyPage.tsx # Privacy policy content
β β βββ ResumePage.tsx # Tabbed resume/skills section
β β βββ ServicesPage.tsx # Service cards grid
β β βββ TermsPage.tsx # Terms of service content
β β βββ WorkPage.tsx # Projects showcase with carousel
β β
β βββ ui/ # Reusable UI components (Shadcn)
β β βββ accordion.tsx # FAQ accordion component
β β βββ alert.tsx # Alert/notification component
β β βββ button.tsx # Custom button variants
β β βββ card.tsx # Card component
β β βββ dropdown-menu.tsx # Dropdown menu
β β βββ input.tsx # Form input field
β β βββ scroll-area.tsx # Custom scrollbar
β β βββ select.tsx # Dropdown select
β β βββ sheet.tsx # Mobile navigation sheet
β β βββ tabs.tsx # Tab navigation
β β βββ textarea.tsx # Multi-line input
β β βββ tooltip.tsx # Tooltip component
β β
β βββ LanguageSelector/ # Language switcher component
β β βββ LanguageSelector.tsx
β β
β βββ Footer.tsx # Footer with links
β βββ GoogleAnalytics.tsx # GA4 integration
β βββ Header.tsx # Main navigation header
β βββ I18nProvider.tsx # i18n provider wrapper
β βββ MobileNav.tsx # Mobile hamburger menu
β βββ Nav.tsx # Desktop navigation links
β βββ PageTransition.tsx # Page animation wrapper
β βββ Photo.tsx # Profile photo with effects
β βββ ScrollToTop.tsx # Scroll-to-top button
β βββ Social.tsx # Social media links
β βββ Stairs.tsx # Stair animation component
β βββ StairTranstion.tsx # Stair transition wrapper
β βββ Stats.tsx # Animated statistics counter
β
βββ context/ # React Context Providers
β βββ LanguageContext.tsx # Language state management
β
βββ hooks/ # Custom React Hooks
β βββ useTypewriter.ts # Typewriter text effect hook
β
βββ lib/ # Utility functions & configurations
β βββ i18n.ts # i18next configuration
β βββ language-cookie.ts # Cookie-based language persistence
β βββ language-detection.ts # Browser language detection
β βββ translations.ts # Translation strings (en/de)
β βββ utils.ts # Helper functions (cn, etc.)
β
βββ public/ # Static assets
β βββ assets/ # Images, icons, etc.
β β βββ resume/ # Resume-related assets
β β βββ skills/ # Skill icons
β β βββ work/ # Project screenshots
β βββ favicon.ico # Site favicon
β βββ photo.png # Profile photo
β βββ robots.txt # SEO robots file
β
βββ .env.local # Environment variables (not in repo)
βββ .eslintignore # ESLint ignore rules
βββ .eslintrc.json # ESLint configuration
βββ .gitignore # Git ignore rules
βββ global.d.ts # Global TypeScript declarations
βββ LICENSE # MIT License
βββ middleware.ts # Next.js middleware (language detection)
βββ next.config.js # Next.js configuration
βββ package.json # Project dependencies
βββ postcss.config.mjs # PostCSS configuration
βββ tailwind.config.js # TailwindCSS configuration
βββ tsconfig.json # TypeScript configuration
βββ README.md # This fileBefore you begin, ensure you have the following installed:
- Node.js 18.17 or higher (Download)
- npm (comes with Node.js) or yarn or pnpm
- Git for version control
- A Gmail account (for email functionality)
- A Google Analytics 4 account (optional, for analytics)
# Clone using HTTPS
git clone https://github.com/arnobt78/MyPortfolio--NextJS-FullStack-Website.git
# Or clone using SSH
git clone git@github.com:arnobt78/MyPortfolio--NextJS-FullStack-Website.git
# Navigate to project directory
cd MyPortfolio--NextJS-FullStack-Website# Using npm
npm install
# Or using yarn
yarn install
# Or using pnpm
pnpm installThis will install all the required dependencies listed in package.json.
Create a .env.local file in the root directory of your project. This file contains sensitive information and should never be committed to version control.
# =================================
# EMAIL CONFIGURATION (Required)
# =================================
# Your Gmail address (used for sending/receiving contact form emails)
EMAIL_USER=your-email@gmail.com
# Gmail App Password (NOT your regular Gmail password)
# Generate this from: https://myaccount.google.com/apppasswords
EMAIL_PASS=your-16-character-app-password
# =================================
# GOOGLE ANALYTICS (Optional)
# =================================
# Google Analytics 4 Measurement ID
# Find this in GA4: Admin > Data Streams > Your Stream
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
# =================================
# CHATBOT WIDGET (Optional)
# =================================
# Chatbot widget URL (for embedded chatbot functionality)
# In production: Your chatbot deployment URL
# In development: http://localhost:3000 (if running locally)
NEXT_PUBLIC_CHATBOT_URL=https://your-chatbot-url.vercel.app
# =================================
# SEO & VERIFICATION (Optional)
# =================================
# Google Search Console verification code
# Get from: https://search.google.com/search-console
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION=your-verification-code
# Bing Webmaster Tools verification code
# Get from: https://www.bing.com/webmasters
NEXT_PUBLIC_BING_SITE_VERIFICATION=your-verification-code- Simply use your existing Gmail address
- Example:
your-email@gmail.com
Important: This is NOT your regular Gmail password. It's a special 16-character password generated by Google.
Steps to generate:
- Go to Google Account Settings
- Click on Security in the left sidebar
- Enable 2-Step Verification (required for App Passwords)
- Once 2FA is enabled, return to Security settings
- Scroll down to App passwords (may appear after 2FA setup)
- Click App passwords
- Select Mail as the app
- Select Other (Custom name) as the device
- Enter a name like "Portfolio Website"
- Click Generate
- Copy the 16-character password (format:
xxxx xxxx xxxx xxxx) - Remove spaces and use it as
EMAIL_PASS
Example: abcdwxyzpqrsjklm
Security Note: Never share your app password or commit it to version control!
Steps to get your Measurement ID:
- Go to Google Analytics
- Create an account if you don't have one
- Create a new GA4 Property (not Universal Analytics)
- Navigate to Admin (gear icon at bottom left)
- Under Property, click Data Streams
- Click on your Web stream
- Find Measurement ID (format:
G-XXXXXXXXXX) - Copy and paste into
.env.local
Example: G-7CTQNDTW0G
If you're using the embedded chatbot widget:
- Production: Your chatbot deployment URL (e.g.,
https://portfolio-chatbot-widget.vercel.app) - Development:
http://localhost:3000(if running chatbot locally)
Steps:
- Go to Google Search Console
- Add your property (website URL)
- Choose HTML tag verification method
- Copy the
contentvalue from the meta tag - Add to
.env.local
Example: abc123def456ghi789
Steps:
- Go to Bing Webmaster Tools
- Add your site
- Choose Meta tag verification
- Copy the
contentvalue - Add to
.env.local
.env.local to Git!
The .gitignore file is already configured to exclude:
.env.local.env.development.local.env.test.local.env.production.local
If you accidentally commit sensitive data:
- Remove it from Git history immediately
- Regenerate all compromised credentials
- Update
.env.localwith new credentials - Rotate any exposed API keys or passwords
Start the development server with hot-reload:
# Using npm
npm run dev
# Or using yarn
yarn dev
# Or using pnpm
pnpm devThe application will be available at:
- Local: http://localhost:3000
- Network: Check terminal for network URL
Note: The project uses Turbopack for faster development builds.
If you encounter cache issues, run:
npm run dev:cleanThis removes the .next folder and starts a fresh development build.
Build the application for production:
# Build the project
npm run build
# Start production server
npm run startCheck code quality and fix issues:
npm run lintThis project includes full internationalization support with English and German languages.
-
Language Detection:
- Checks browser language preference
- Falls back to cookie-stored preference
- Defaults to English if no preference found
-
Language Persistence:
- Stores selected language in cookies
- Persists across page reloads
- Uses
selectedLanguagecookie key
-
Translation System:
- Uses
i18nextandreact-i18next - Translation strings stored in
lib/translations.ts - Supports nested keys (e.g.,
home.hello)
- Uses
- Add translations to
lib/translations.ts:
export const translations = {
en: {
"nav.home": "Home",
// ... existing translations
},
de: {
"nav.home": "Startseite",
// ... existing translations
},
// Add new language
fr: {
"nav.home": "Accueil",
// ... add all translation keys
},
};- Update language types:
// In context/LanguageContext.tsx
export type Language = "en" | "de" | "fr"; // Add new language
// In middleware.ts
const supportedLanguages = ["en", "de", "fr"]; // Add new language- Update i18n configuration:
// In lib/i18n.ts
resources: {
en: { translation: translations.en },
de: { translation: translations.de },
fr: { translation: translations.fr }, // Add new language
},import { useLanguage } from "@/context/LanguageContext";
function MyComponent() {
const { t } = useLanguage();
return (
<div>
<h1>{t("home.hello")}</h1>
<p>{t("home.bio")}</p>
</div>
);
}The LanguageSelector component provides a dropdown to switch languages:
import { LanguageSelector } from "@/components/LanguageSelector/LanguageSelector";
<LanguageSelector />;Purpose: Landing page hero section with introduction and call-to-action.
Key Features:
- Typewriter effect for name animation
- Profile photo with circular border effect
- Download resume button
- Social media links
- Animated statistics counter
Code Example:
import HomePage from "@/components/pages/HomePage";
export default function Home() {
return <HomePage />;
}Reusability:
// Extract typewriter effect
import { useTypewriter } from "@/hooks/useTypewriter";
const { displayText, isComplete } = useTypewriter({
text: "Your Name",
speed: 200,
delay: 2000,
});Customization:
- Edit personal information in the component
- Update resume link in the Button href
- Modify typewriter text in
useTypewriterhook - Change animation delays in style props
Purpose: Display services offered in a grid layout with hover effects.
Key Features:
- Responsive grid (1 column mobile, 2 columns desktop)
- Hover effects with color transitions
- Arrow icon that rotates on hover
- Service cards with numbering
- Technology stack display
Data Structure:
interface Service {
num: string; // Service number (e.g., "01")
titleKey: string; // Translation key for title
descriptionKey: string; // Translation key for description
stack: ServiceStack[]; // Technologies used
href: string; // Link to contact page
}How to Add/Edit Services:
const services: Service[] = [
{
num: "01",
titleKey: "services.01.title",
descriptionKey: "services.01.description",
stack: [{ name: "React" }, { name: "Next.js" }],
href: "/contact",
},
// Add more services here
];Reusability in Other Projects:
- Extract the service card into a separate component
- Pass services array as props
- Style using Tailwind utility classes
- Perfect for service pages, product showcases, or feature lists
Purpose: Tabbed interface displaying resume, experience, education, and skills.
Key Features:
- Tab navigation (About, Experience, Education, Skills)
- Scrollable content areas
- Icon-based skill display with tooltips
- Timeline cards for experience/education
- Multi-language support
Data Structures:
// About section
interface InfoItem {
fieldName: string;
fieldValue: string;
}
// Experience section
interface ExperienceItem {
company: string;
position: string;
duration: string;
}
// Skills section
interface SkillItem {
icon: JSX.Element;
name: string;
}How to Update Content:
- Add Experience:
const experience: ExperienceData = {
items: [
{
company: "Your Company",
position: "Your Position",
duration: "Jan 2024 - Present",
},
],
};- Add Skills:
import { FaReact } from "react-icons/fa";
const skills: SkillsData = {
skillList: [
{ icon: <FaReact />, name: "React.js" },
// Add more skills
],
};Reusability:
- Convert to a generic tabbed component
- Pass data as props
- Use in team pages, product showcases, or documentation sites
Purpose: Showcase portfolio projects with grid/list view toggle.
Key Features:
- Swiper carousel for image slideshow
- Grid view (original) and list view modes
- Project filtering by category
- Live demo and GitHub repository links
- Technology stack display
- Multi-language project descriptions
Project Data Structure:
interface Project {
num: string; // Project number
category: string; // Frontend/Fullstack/Backend
title: string; // Translation key for title
description: string; // Translation key for description
stack: ProjectStack[]; // Technologies used
image: string; // Screenshot path
live: string; // Live demo URL
github: string; // GitHub repository URL
}How to Add New Projects:
const projects: Project[] = [
{
num: "01",
category: "Fullstack",
title: "work.project.01.title", // Translation key
description: "work.project.01.description", // Translation key
stack: [{ name: "Next.js" }, { name: "TypeScript" }],
image: "/assets/work/project-image.png",
live: "https://your-live-demo.com",
github: "https://github.com/yourusername/repo",
},
];View Mode Toggle:
- Grid View: Carousel with single project focus
- List View: All projects in scrollable list
Reusability:
- Perfect for freelancer portfolios
- Agency project showcases
- Product galleries
- Case study presentations
- E-commerce product listings
Purpose: Contact form with email functionality and validation.
Key Features:
- Form validation (required fields, email format)
- Loading states during submission
- Success/error alerts with icons
- Auto-reply email to user
- Smooth scroll to alert message
- Contact information display
- Copy-to-clipboard functionality
- Multi-language support
Form Data Structure:
interface FormData {
fullname: string;
email: string;
message: string;
}API Integration:
// Send main email
const response = await axios.post("/api/send-email", formData);
// Send auto-reply
const autoReply = await axios.post("/api/send-auto-reply", formData);Error Handling:
- Network errors
- Authentication errors
- Validation errors
- Timeout errors
Reusability:
- Extract form into separate component
- Add file upload capability
- Integrate with other backend services (Firebase, Supabase)
- Add CAPTCHA for spam protection
- Use in newsletter signups, feedback forms, or support tickets
Purpose: Frequently Asked Questions page with accordion interface.
Key Features:
- Accordion component for expandable Q&A
- Multi-language support
- Smooth expand/collapse animations
- Accessible keyboard navigation
Data Structure:
interface FAQItem {
question: string; // Translation key
answer: string; // Translation key
}How to Add FAQs:
const faqData: FAQItem[] = [
{
question: t("faq.01.question"),
answer: t("faq.01.answer"),
},
// Add more FAQs
];Reusability:
- Use in help centers
- Documentation sites
- Product pages
- Support pages
These are Shadcn UI components - fully customizable, accessible, and ready to use.
Versatile button component with multiple variants.
Variants:
default- Primary accent buttondestructive- Red danger buttonoutline- Bordered buttonsecondary- Muted secondary buttonghost- Transparent buttonlink- Link-styled button
Sizes:
default- Standard sizesm- Small buttonlg- Large buttonicon- Square icon button
Usage:
import { Button } from "@/components/ui/button";
<Button variant="default" size="lg">
Click Me
</Button>
<Button variant="outline" size="sm">
Secondary Action
</Button>Styled text input field with focus states.
Usage:
import { Input } from "@/components/ui/input";
<Input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>;Multi-line text input for longer content.
Usage:
import { Textarea } from "@/components/ui/textarea";
<Textarea placeholder="Your message" className="h-[200px]" />;Notification component for success/error messages.
Variants:
default- Blue informationaldestructive- Red errorsuccess- Green success (custom)
Usage:
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
<Alert variant="success">
<AlertTitle>Success!</AlertTitle>
<AlertDescription>Your message has been sent.</AlertDescription>
</Alert>;Tabbed interface for organizing content.
Usage:
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
<Tabs defaultValue="tab1">
<TabsList>
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
</TabsList>
<TabsContent value="tab1">Content for Tab 1</TabsContent>
<TabsContent value="tab2">Content for Tab 2</TabsContent>
</Tabs>;Expandable accordion component for FAQs or collapsible content.
Usage:
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
<Accordion type="single" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger>Question?</AccordionTrigger>
<AccordionContent>Answer here.</AccordionContent>
</AccordionItem>
</Accordion>;Hover tooltip for additional information.
Usage:
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
<TooltipProvider>
<Tooltip>
<TooltipTrigger>Hover me</TooltipTrigger>
<TooltipContent>
<p>Tooltip content here</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>;Main navigation header with logo and links.
Features:
- Logo with accent dot
- Desktop navigation menu
- Mobile hamburger menu
- Language selector
- "Hire me" CTA button
Customization:
// Change logo text
<h1 className="text-4xl font-semibold">
YourBrand<span className="text-accent">.</span>
</h1>Desktop navigation links with active state highlighting.
Navigation Links:
const links = [
{ name: "home", path: "/" },
{ name: "services", path: "/services" },
{ name: "resume", path: "/resume" },
{ name: "work", path: "/work" },
{ name: "contact", path: "/contact" },
];How to Add Links:
Simply add to the links array with name and path. The component uses translation keys for display.
Mobile-friendly sheet navigation.
Features:
- Hamburger icon trigger
- Slide-in navigation sheet
- Logo display
- Close button
- Mobile-optimized link styles
Footer component with copyright and links.
Features:
- Dynamic year display
- Links to About, Privacy, Terms pages
- Multi-language support
- Responsive layout
Social media icon links.
Social Platforms:
const socials = [
{ icon: <FaGithub />, path: "https://github.com/username" },
{ icon: <FaLinkedinIn />, path: "https://linkedin.com/in/username" },
{ icon: <FaYoutube />, path: "https://youtube.com/@username" },
{ icon: <FaInstagram />, path: "https://instagram.com/username" },
];Usage:
<Social
containerStyles="flex gap-6"
iconStyles="w-9 h-9 border border-accent rounded-full flex justify-center items-center text-accent hover:bg-accent hover:text-primary transition-all duration-500"
/>Animated statistics counter.
Features:
- Counts from start value to target value
- Smooth easing animation
- Responsive grid layout
- Customizable duration
- Multi-language support
Data Structure:
const stats = [
{
num: 5, // Target number
textKey: "home.stats.years", // Translation key
startFrom: 0, // Starting number
},
];How It Works:
Uses requestAnimationFrame for smooth 60fps animation with ease-out curve.
Reusability:
- E-commerce dashboards
- Analytics displays
- Achievement counters
- Progress indicators
Profile photo with circular border animation.
Features:
- Circular shape with rotating border effect
- Responsive sizing
- Image optimization with Next.js Image
- Fade-in animation
Customization:
// Change image source
<Image
src="/your-photo.png"
alt="Your Name"
width={498}
height={498}
priority
/>Wrapper component for page transition animations.
Usage:
import PageTransition from "@/components/PageTransition";
export default function RootLayout({ children }) {
return <PageTransition>{children}</PageTransition>;
}Animation:
Uses Framer Motion's AnimatePresence with fade and slide effects.
Creative stair-step page transition effect.
How It Works:
- Creates multiple div elements
- Animates them in sequence
- Creates a "stair" effect during page transitions
Button that appears when scrolling down, returns to top when clicked.
Features:
- Only visible after scrolling 300px
- Smooth scroll to top
- Fixed position in bottom-right corner
- Fade in/out animation
Google Analytics 4 integration component.
Features:
- Loads GA4 script
- Initializes
gtagfunction - Logs status in development mode
- Silent fail for ad blockers
Usage:
Already included in layout.tsx root layout.
Language switcher dropdown component.
Features:
- Dropdown menu for language selection
- Visual flag/icons for languages
- Persists selection in cookies
- Updates UI immediately
Usage:
import { LanguageSelector } from "@/components/LanguageSelector/LanguageSelector";
<LanguageSelector />;Purpose: Sends contact form submission to your email address.
Request Body:
{
fullname: string; // User's name (1-100 characters)
email: string; // User's email (valid format)
message: string; // User's message (1-5000 characters)
}Response (Success):
{
message: "Email sent successfully";
}Response (Error):
{
error: "Validation failed" | "Authentication failed" | "Connection failed",
details: string // Specific error message
}Validation Rules:
- All fields required
- Email must be valid format
- Name: 1-100 characters
- Message: 1-5000 characters
- Input sanitization to prevent XSS
Email Configuration:
const transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 587,
secure: false,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});Error Handling:
EAUTH- Authentication failedECONNECTION- Connection failed- Invalid email format
- Missing fields
Usage Example:
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const response = await axios.post("/api/send-email", formData);
if (response.status === 200) {
// Show success message
}
} catch (error) {
// Handle error
}
};Purpose: Sends automatic confirmation email to user after form submission.
Request Body:
{
fullname: string;
email: string;
message: string;
}Response (Success):
{
message: "Auto-reply sent successfully",
referenceNumber: string // Format: ARN-{timestamp}-{random}
}Email Template Features:
- Professional HTML email design
- Reference number for tracking
- Message preview (truncated at 200 chars)
- Submission date
- Contact information
- Brand colors and fonts
- Responsive design
Sample Reference Number:
ARN-1729699200000-742HTML Email Template:
- Header with gradient background
- Message preview box
- Reference number display
- Next steps section
- Contact information footer
- Disclaimer section
Usage Example:
// Send auto-reply after main email
const autoReply = await axios.post("/api/send-auto-reply", formData);
console.log("Reference:", autoReply.data.referenceNumber);Next.js 15 uses the App Router with file-based routing.
| Route | File | Component | Description |
|---|---|---|---|
/ |
app/page.tsx |
HomePage |
Landing page with hero |
/about |
app/about/page.tsx |
AboutPage |
About information |
/services |
app/services/page.tsx |
ServicesPage |
Services offered |
/resume |
app/resume/page.tsx |
ResumePage |
Resume/Skills |
/work |
app/work/page.tsx |
WorkPage |
Portfolio projects |
/faq |
app/faq/page.tsx |
FAQPage |
Frequently asked questions |
/contact |
app/contact/page.tsx |
ContactPage |
Contact form |
/privacy |
app/privacy/page.tsx |
PrivacyPage |
Privacy policy |
/terms |
app/terms/page.tsx |
TermsPage |
Terms of service |
Example: Add /blog page
- Create folder and file:
mkdir app/blog
touch app/blog/page.tsx- Create page component:
// app/blog/page.tsx
export default function BlogPage() {
return (
<div>
<h1>Blog</h1>
{/* Your blog content */}
</div>
);
}- Add to navigation:
// components/Nav.tsx
const links = [
// ... existing links
{ name: "blog", path: "/blog" },
];Example: Blog post with dynamic slug
mkdir -p app/blog/[slug]
touch app/blog/[slug]/page.tsx// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
return (
<div>
<h1>Blog Post: {params.slug}</h1>
</div>
);
}Access at: /blog/my-first-post
The project includes automatic sitemap generation via app/sitemap.ts:
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: "https://www.arnobmahmud.com",
lastModified: new Date(),
changeFrequency: "monthly",
priority: 1,
},
// ... more routes
];
}Access at: /sitemap.xml
File: hooks/useTypewriter.ts
Purpose: Creates a typewriter text effect with customizable speed and delay.
Interface:
interface UseTypewriterOptions {
text: string; // Text to animate
speed?: number; // Typing speed in ms (default: 100)
delay?: number; // Initial delay before typing starts (default: 0)
}
interface UseTypewriterReturn {
displayText: string; // Currently displayed text
isComplete: boolean; // Whether animation is complete
}Usage:
import { useTypewriter } from "@/hooks/useTypewriter";
function MyComponent() {
const { displayText, isComplete } = useTypewriter({
text: "Hello, World!",
speed: 150,
delay: 1000,
});
return (
<h1>
{displayText}
{!isComplete && <span className="typewriter-cursor">|</span>}
</h1>
);
}How It Works:
- Delays initial render by
delaymilliseconds - Types one character every
speedmilliseconds - Updates
displayTextstate progressively - Sets
isCompletetotruewhen finished
Reusability:
- Hero headlines
- Product descriptions
- Loading messages
- Interactive tutorials
- Chat interfaces
Customization Ideas:
- Add backspace/delete effect
- Loop animation
- Type multiple strings in sequence
- Add realistic typing pauses
File: tailwind.config.js
Custom Theme:
theme: {
extend: {
colors: {
primary: "#1c1c22",
accent: {
DEFAULT: "#00ff99",
hover: "#00e187",
},
},
fontFamily: {
primary: "var(--font-jetbrainsMono)",
},
}
}Custom Animations:
keyframes: {
"fade-in": {
from: { opacity: "0" },
to: { opacity: "1" },
},
"ease-in-out": {
"0%": { opacity: "0", transform: "translateY(10px)" },
"100%": { opacity: "1", transform: "translateY(0)" },
},
}File: app/globals.css
Key Features:
- Custom animations with delays
- Scrollbar styling
- Text outline effects
- Typewriter cursor animation
- Horizontal scroll prevention
Typewriter Cursor:
.typewriter-cursor {
display: inline-block;
width: 2px;
height: 1em;
background-color: #00ff99;
margin-left: 2px;
animation: blink 1s infinite;
}
@keyframes blink {
0%,
49% {
opacity: 1;
}
50%,
100% {
opacity: 0;
}
}Custom Scrollbar:
.custom-scrollbar::-webkit-scrollbar {
width: 8px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #1c1c22;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #00ff99;
border-radius: 4px;
}Page Transitions:
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{ duration: 0.5 }}
>
{children}
</motion.div>Hover Effects:
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
Click me
</motion.div>Staggered Children:
<motion.div
variants={{
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
}}
>
{items.map((item) => (
<motion.div
variants={{
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 },
}}
>
{item}
</motion.div>
))}
</motion.div>Step 1: Enable 2-Step Verification
- Go to Google Account Security
- Click "2-Step Verification"
- Follow the setup wizard
Step 2: Generate App Password
- Return to Security settings
- Click "App passwords"
- Select "Mail" and "Other (Custom name)"
- Enter "Portfolio Website"
- Click "Generate"
- Copy the 16-character password
- Add to
.env.localasEMAIL_PASS
Step 3: Verify Configuration
// Test email configuration
const transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 587,
secure: false,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
// Verify connection
await transporter.verify();const transporter = nodemailer.createTransport({
host: "smtp.mail.yahoo.com",
port: 587,
secure: false,
auth: {
user: "youremail@yahoo.com",
pass: "your-app-password",
},
});const transporter = nodemailer.createTransport({
host: "smtp-mail.outlook.com",
port: 587,
secure: false,
auth: {
user: "youremail@outlook.com",
pass: "your-password",
},
});const transporter = nodemailer.createTransport({
host: "smtp.yourdomain.com",
port: 465,
secure: true,
auth: {
user: "youremail@yourdomain.com",
pass: "your-password",
},
});Error: "Invalid login"
- Ensure 2FA is enabled
- Regenerate app password
- Check for typos in
.env.local
Error: "Connection timeout"
- Check firewall settings
- Verify port 587 is open
- Try port 465 with
secure: true
Emails going to spam
- Add SPF record to your domain
- Set up DKIM authentication
- Use authenticated sender in "from" field
Setup in Project:
- Component already created:
components/GoogleAnalytics.tsx - Imported in
app/layout.tsx - Uses
NEXT_PUBLIC_GA_MEASUREMENT_IDfrom.env.local
Tracking Events:
// Custom event tracking
window.gtag("event", "button_click", {
event_category: "engagement",
event_label: "hire_me_button",
value: 1,
});Page Views:
Automatically tracked on route changes.
View Reports:
- Go to Google Analytics
- Select your property
- Navigate to "Reports" > "Realtime" for live data
- Navigate to "Reports" > "Engagement" for detailed analytics
Setup:
Already integrated in app/layout.tsx:
import { Analytics } from "@vercel/analytics/react";
export default function RootLayout({ children }) {
return (
<html>
<body>
<Analytics />
{children}
</body>
</html>
);
}Features:
- Automatic page view tracking
- Web Vitals monitoring (CLS, FID, LCP, FCP, TTFB)
- No configuration needed
- Works automatically on Vercel deployments
View Analytics:
- Go to Vercel Dashboard
- Select your project
- Click "Analytics" tab
- View traffic, performance, and Web Vitals
File: app/layout.tsx
The project includes comprehensive SEO metadata:
export const metadata: Metadata = {
metadataBase: new URL("https://www.arnobmahmud.com"),
title: "Arnob Mahmud | Full-Stack Engineer | Web & Cloud Solutions",
description: "Full-Stack Software Engineer (5+ years)...",
keywords: [
"Full-Stack Software Engineer",
"React",
"Next.js",
// ... more keywords
],
openGraph: {
title: "...",
description: "...",
url: "https://www.arnobmahmud.com",
images: [{ url: "/assets/photo.png" }],
},
twitter: {
card: "summary_large_image",
// ... Twitter metadata
},
};The project includes two structured data schemas:
- Person Schema - For personal information
- LocalBusiness Schema - For business information
These help search engines understand your content better.
Automatic sitemap generation at /sitemap.xml via app/sitemap.ts.
Located at public/robots.txt for search engine crawling instructions.
Option 1: Deploy via GitHub (Automatic)
- Push to GitHub:
git add .
git commit -m "Initial commit"
git push origin main-
Connect to Vercel:
- Go to Vercel
- Click "Add New" > "Project"
- Import your GitHub repository
- Vercel auto-detects Next.js
-
Configure Environment Variables:
- Click "Environment Variables"
- Add all variables from
.env.local:EMAIL_USEREMAIL_PASSNEXT_PUBLIC_GA_MEASUREMENT_IDNEXT_PUBLIC_CHATBOT_URL(if using)NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION(optional)NEXT_PUBLIC_BING_SITE_VERIFICATION(optional)
- Click "Deploy"
-
Auto-Deployment:
- Every push to
mainbranch triggers new deployment - Pull requests create preview deployments
- Every push to
Option 2: Deploy via Vercel CLI
# Install Vercel CLI
npm install -g vercel
# Login to Vercel
vercel login
# Deploy
vercel
# Deploy to production
vercel --prod-
Build Settings:
- Build command:
npm run build - Publish directory:
.next
- Build command:
-
Environment Variables:
Add all variables from
.env.localin Netlify dashboard -
Netlify Configuration:
Create
netlify.toml:
[build]
command = "npm run build"
publish = ".next"
[[plugins]]
package = "@netlify/plugin-nextjs"- Connect GitHub repository
- Configure build settings:
- Build command:
npm run build - Output directory:
.next
- Build command:
- Add environment variables
- Deploy
Requirements:
- Node.js 18+ installed
- PM2 for process management
- Nginx for reverse proxy
Steps:
- Build the application:
npm run build- Start with PM2:
# Install PM2
npm install -g pm2
# Start application
pm2 start npm --name "portfolio" -- start
# Save PM2 configuration
pm2 save
# Set up auto-start
pm2 startup- Configure Nginx:
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}- Enable SSL with Certbot:
sudo certbot --nginx -d yourdomain.comExtract from ServicesPage:
// components/ServiceCard.tsx
import Link from "next/link";
import { BsArrowDownRight } from "react-icons/bs";
interface ServiceCardProps {
num: string;
title: string;
description: string;
href: string;
}
export default function ServiceCard({
num,
title,
description,
href,
}: ServiceCardProps) {
return (
<div className="flex-1 flex flex-col justify-between gap-4 group">
<div className="w-full flex justify-between items-center">
<div className="text-5xl font-extrabold text-outline text-transparent group-hover:text-outline-hover transition-all duration-500">
{num}
</div>
<Link
href={href}
className="w-[50px] h-[50px] rounded-full bg-white group-hover:bg-accent transition-all duration-500 flex justify-center items-center hover:-rotate-45"
>
<BsArrowDownRight className="text-primary text-3xl" />
</Link>
</div>
<h2 className="text-[32px] font-bold leading-none text-white group-hover:text-accent transition-all duration-500">
{title}
</h2>
<p className="text-white/60 text-justify">{description}</p>
<div className="border-b border-white/20 w-full"></div>
</div>
);
}Usage:
import ServiceCard from '@/components/ServiceCard';
const services = [...];
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-16">
{services.map((service, index) => (
<ServiceCard key={index} {...service} />
))}
</div>
);Extract from ContactPage:
// components/ContactForm.tsx
import { useState } from "react";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Textarea } from "./ui/textarea";
interface ContactFormProps {
onSubmit: (data: FormData) => Promise<void>;
loading?: boolean;
}
interface FormData {
fullname: string;
email: string;
message: string;
}
export default function ContactForm({
onSubmit,
loading = false,
}: ContactFormProps) {
const [formData, setFormData] = useState<FormData>({
fullname: "",
email: "",
message: "",
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await onSubmit(formData);
setFormData({ fullname: "", email: "", message: "" });
};
return (
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<Input
type="text"
name="fullname"
placeholder="Enter your name"
value={formData.fullname}
onChange={(e) =>
setFormData((prev) => ({ ...prev, fullname: e.target.value }))
}
required
/>
<Input
type="email"
name="email"
placeholder="Enter your email"
value={formData.email}
onChange={(e) =>
setFormData((prev) => ({ ...prev, email: e.target.value }))
}
required
/>
<Textarea
name="message"
placeholder="Type your message here"
value={formData.message}
onChange={(e) =>
setFormData((prev) => ({ ...prev, message: e.target.value }))
}
className="h-[200px]"
required
/>
<Button type="submit" disabled={loading}>
{loading ? "Sending..." : "Send Message"}
</Button>
</form>
);
}Make Stats.tsx more flexible:
// components/StatsCounter.tsx
interface Stat {
num: number;
text: string;
startFrom?: number;
}
interface StatsCounterProps {
stats: Stat[];
columns?: number;
}
export default function StatsCounter({
stats,
columns = 4,
}: StatsCounterProps) {
const gridClass = `grid-cols-${columns}`;
return (
<div className={`grid ${gridClass} gap-6`}>
{stats.map((stat, index) => (
<StatItem key={index} {...stat} />
))}
</div>
);
}Usage in different projects:
// E-commerce dashboard
const ecommerceStats = [
{ num: 1250, text: "Total Orders" },
{ num: 45678, text: "Revenue ($)" },
{ num: 98, text: "Customer Satisfaction (%)" },
];
<StatsCounter stats={ecommerceStats} columns={3} />;
// Blog analytics
const blogStats = [
{ num: 50000, text: "Monthly Visitors" },
{ num: 234, text: "Published Articles" },
];
<StatsCounter stats={blogStats} columns={2} />;β Make components prop-driven
- Accept data via props instead of hardcoding
- Use TypeScript interfaces for type safety
β Keep components focused
- Single responsibility principle
- Separate logic from presentation
β Use composition over inheritance
- Build complex UIs from simple components
- Use children prop for flexibility
β Document component APIs
- Add JSDoc comments
- Provide usage examples
- List all props and their types
β Style with flexibility
- Accept className prop for custom styling
- Use Tailwind utility classes
- Support theme variants
β Handle edge cases
- Empty states
- Loading states
- Error states
1. Component Structure:
Component/
βββ Component.tsx # Main component
βββ Component.test.tsx # Unit tests
βββ Component.stories.tsx # Storybook stories (optional)
βββ index.ts # Export file2. Import Order:
// 1. React and Next.js imports
import { useState } from "react";
import Link from "next/link";
// 2. Third-party libraries
import axios from "axios";
import { motion } from "framer-motion";
// 3. Internal components
import { Button } from "@/components/ui/button";
// 4. Utilities and helpers
import { cn } from "@/lib/utils";
// 5. Types and interfaces
import type { FormData } from "@/types";
// 6. Styles and assets
import "./styles.css";3. TypeScript Best Practices:
- Always define interfaces for props
- Use
typefor unions and intersections - Avoid
anytype - Enable strict mode
4. Performance Optimization:
- Use
React.memofor expensive components - Implement code splitting with
dynamicimports - Optimize images with Next.js
Imagecomponent - Lazy load components below the fold
5. Accessibility:
- Use semantic HTML elements
- Add ARIA labels where needed
- Ensure keyboard navigation
- Maintain color contrast ratios
- Test with screen readers
6. SEO Best Practices:
- Use Next.js metadata API
- Include Open Graph tags
- Add structured data (JSON-LD)
- Create sitemap.xml
- Implement canonical URLs
File: app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL("https://www.arnobmahmud.com"),
title: "Arnob Mahmud | Full-Stack Developer | Portfolio",
description: "Professional portfolio showcasing web development projects...",
keywords: [
"Arnob Mahmud",
"Full-Stack Developer",
"Web Developer",
"React",
"Next.js",
"TypeScript",
"Portfolio",
],
authors: [{ name: "Arnob Mahmud" }],
openGraph: {
title: "Arnob Mahmud | Full-Stack Developer",
description: "Portfolio of Arnob Mahmud...",
url: "https://www.arnobmahmud.com",
siteName: "Arnob Mahmud Portfolio",
images: [{ url: "/assets/photo.png" }],
locale: "en_US",
type: "website",
},
twitter: {
card: "summary_large_image",
title: "Arnob Mahmud | Full-Stack Developer",
description: "Portfolio of Arnob Mahmud...",
images: ["/assets/photo.png"],
},
};General:
- Full-Stack Developer
- Web Developer
- Software Engineer
- Frontend Developer
- Backend Developer
- Portfolio Website
Technologies:
- React.js
- Next.js
- TypeScript
- JavaScript
- Node.js
- TailwindCSS
- Framer Motion
Services:
- Web Development
- UI/UX Design
- API Development
- Database Design
- Cloud Deployment
- SEO Optimization
Location-based:
- Frankfurt Developer
- Germany Web Developer
- Remote Developer
Symptoms:
- "Authentication failed" error
- "Connection timeout" error
- Emails not arriving
Solutions:
# Check environment variables
echo $EMAIL_USER
echo $EMAIL_PASS
# Verify .env.local exists
ls -la .env.local
# Restart development server
npm run dev:cleanChecklist:
- β 2FA enabled on Gmail
- β App password generated (not regular password)
- β No spaces in app password
- β
Correct email in
EMAIL_USER - β Port 587 not blocked by firewall
Symptoms:
- No data in GA4 dashboard
- Real-time reports empty
Solutions:
- Check Measurement ID:
# .env.local
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX- Verify in Browser Console:
// Check if gtag is loaded
console.log(window.gtag);
console.log(window.dataLayer);- Disable Ad Blocker:
- Ad blockers prevent GA from loading
- Test in incognito mode without extensions
- Wait 24-48 hours:
- GA4 data processing takes time
- Real-time reports update faster
Error: "Module not found"
# Clear node_modules and reinstall
rm -rf node_modules
rm package-lock.json
npm installError: "Type error in component"
# Check TypeScript errors
npm run build
# Fix type errors in the reported filesError: "Cannot find module './public/...'"
# Ensure asset files exist
ls -la public/assets/
# Check file paths are correct (case-sensitive)Tailwind classes not working:
# Restart development server
npm run dev:clean
# Check tailwind.config.js includes your files
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
]Custom fonts not loading:
// Verify font is imported in layout.tsx
import { JetBrains_Mono } from "next/font/google";
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-jetbrainsMono",
});Environment variables not working:
- Go to Vercel Dashboard
- Project Settings β Environment Variables
- Add all variables
- Redeploy the project
Build fails on Vercel:
# Check build works locally
npm run build
# Review build logs in Vercel dashboard
# Fix reported errorsLanguage not persisting:
- Check cookie settings in browser
- Verify
middleware.tsis properly configured - Check
LanguageContext.tsxfor cookie setting logic
Translations not loading:
- Verify
lib/translations.tshas all required keys - Check
lib/i18n.tsconfiguration - Ensure
I18nProviderwraps the app inlayout.tsx
This is an open-source project and contributions are welcome! Whether you want to:
- π Fix bugs
- β¨ Add new features
- π Improve documentation
- π¨ Enhance UI/UX
- β‘ Optimize performance
- π§ Refactor code
- π Add translations
- π‘ Suggest improvements
Feel free to:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes with proper TypeScript types and linting
- Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Use TypeScript with proper type definitions
- Follow ESLint rules (run
npm run lint) - Write clear commit messages
- Add comments for complex logic
- Test your changes before submitting
- Update documentation if needed
- Check if the bug is already reported in Issues
- Create a new issue with:
- Clear title
- Steps to reproduce
- Expected vs actual behavior
- Screenshots (if applicable)
- Environment details (OS, browser, Node version)
- Open a new issue with the label "enhancement"
- Describe the feature and its benefits
- Provide examples or mockups
feat:- New featurefix:- Bug fixdocs:- Documentation changesstyle:- Code style changes (formatting)refactor:- Code refactoringtest:- Adding testschore:- Maintenance tasks
Together, we can take this project to the next level! π
This project is open-source and available under the MIT License.
What this means:
- β Free to use for personal and commercial projects
- β Modify and distribute as you wish
- β Include in private and public repositories
β οΈ Must include the license noticeβ οΈ No warranty provided
For full license details, see the LICENSE file in the repository.
This project demonstrates a production-ready implementation of a modern portfolio website with:
- Modern Architecture: Next.js App Router, Server Components, API Routes
- Best Practices: TypeScript, proper error handling, input validation
- Performance: Image optimization, lazy loading, code splitting
- User Experience: Smooth animations, responsive design, accessibility
- Developer Experience: Well-documented, type-safe, reusable components
- SEO: Comprehensive metadata, structured data, sitemap generation
- Internationalization: Multi-language support with persistence
- Analytics: Google Analytics and Vercel Analytics integration
The codebase is well-documented and structured for easy understanding and extension. Each component can be reused independently in other projects, making this an excellent learning resource and starting point for portfolio websites.
- Next.js 15 provides excellent developer experience with App Router
- TypeScript ensures type safety and better code quality
- TailwindCSS enables rapid UI development
- Framer Motion creates smooth, professional animations
- i18next makes internationalization straightforward
- Nodemailer enables reliable email functionality
- Component reusability saves time in future projects
- Next.js Documentation
- React Documentation
- TypeScript Handbook
- TailwindCSS Docs
- Framer Motion Docs
- i18next Documentation
This is an open-source project - feel free to use, enhance, and extend this project further!
If you have any questions, want to contribute, or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com/.
Enjoy building and learning! π
Thank you! π




