Modern Portfolio Website - Next.js, TypeScript, TailwindCSS, Framer Motion, Shadcn UI, i18next, FullStack Project (My Personal Official Portfolio)
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/
- 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 project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
This is an open-source project - feel free to use, enhance, and extend this project further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.
Enjoy building and learning! π
Thank you! π




