Published on

nasimstg.dev - Building a Modern Full-Stack Portfolio with Next.js and Firebase

Authors

nasimstg.dev: A Modern Portfolio & Project Management System with Real-time Communication

Table of Contents

Introduction

In the rapidly evolving landscape of web development, building a scalable, performant, and feature-rich application requires careful technology selection and thoughtful architectural decisions. This article provides an in-depth technical overview of our web application (nasimstg.dev) built with Next.js, Firebase, and TypeScript. We'll explore the architecture, implementation details, challenges overcome, and our future roadmap.

As a software engineer focused on delivering customized solutions, this project represents our approach to modern web application development — blending robust backend services with an elegant frontend experience.

Project Vision and Goals

Our application serves multiple purposes:

  1. Professional Portfolio - Showcasing projects, skills, and experience
  2. Real-time Communication Platform - Enabling project-based discussions via a chat interface
  3. Client Management System - Managing client relationships and projects
  4. Content Publishing Platform - Sharing technical knowledge through blog posts

The key technical requirements included:

  • Real-time Capabilities - Instant messaging and notifications
  • Secure Authentication - Role-based access control
  • Responsive Design - Seamless experience across devices
  • Performance Optimization - Fast page loads and minimal client-side JavaScript
  • Content Management - Easy-to-update blog and project information
  • Scalability - Ability to handle growth in users and data

Core Technology Stack

Our technology choices were guided by the need for developer productivity, performance, and scalability:

Frontend

  • Next.js 14 - React framework for server-side rendering, static site generation, and API routes
  • TypeScript - For type safety and improved developer experience
  • Tailwind CSS - Utility-first CSS framework for rapid UI development
  • Shadcn/UI - Component library built on Radix UI primitives
  • Lucide Icons - Consistent icon system

Backend

  • Firebase Authentication - User management and security
  • Firestore - Document database for structured data
  • Firebase Realtime Database - Real-time data synchronization for chat features
  • Firebase Cloud Functions - Serverless backend for APIs and background tasks

DevOps & Tools

  • Git & GitHub - Version control and CI/CD
  • ESLint & Prettier - Code quality and formatting
  • Jest & Testing Library - Frontend testing

Application Architecture

System Architecture Overview

The application follows a modern JAMstack architecture with serverless backend services:

┌─────────────────┐     ┌─────────────────────┐     ┌────────────────────┐
│  Next.js App    │     │  Firebase Services  │     │  External Services │
│  ────────────   │     │  ─────────────────  │     │  ────────────────  │
│  - Pages/Routes │───▶ │  - Authentication   │     │  - Email Service   │
│  - Components   │◀─── │  - Firestore        │───▶│  - Payment Gateway │
│  - API Routes   │     │  - Realtime DB      │◀────│  - Analytics       │
│  - Static Gen   │     │  - Cloud Functions  │     │                    │
└─────────────────┘     └─────────────────────┘     └────────────────────┘

Data Flow Architecture

The application uses multiple data flow patterns depending on the feature:

  1. Static Content (Projects, Blog, Team info)

    • Content stored in TypeScript files in the /data directory
    • Built at compile time via Next.js Static Site Generation (SSG)
  2. Dynamic User Data (Auth, User profiles)

    • Firebase Authentication + Firestore for persistence
    • Context API for global state management
  3. Real-time Features (Chat, Notifications)

    • Firebase Realtime Database with WebSocket connections
    • React hooks for reactive UI updates

Directory Structure

Our codebase is organized to maximize separation of concerns:

src/
├── api/            # API routes and integrations
├── app/            # Next.js app directory (pages and routes)
├── components/     # Reusable UI components and business logic
│   ├── Auth/       # Authentication components
│   ├── Chat/       # Chat interface components
│   ├── Context/    # React Context providers
│   └── services/   # Firebase service abstractions
├── data/           # Static data (projects, blog, etc.)
├── hooks/          # Custom React hooks
├── layouts/        # Page layout components
├── lib/            # Utility functions and helpers
├── scripts/        # Build and deployment scripts
└── types/          # TypeScript type definitions

Implementation Deep Dive

Authentication System

Authentication is powered by Firebase Auth with a custom React Context provider that handles user state across the application:

// src/components/Context/AuthContext.tsx (simplified)
export function AuthUserProvider({ children }: AuthUserProviderProps): JSX.Element {
  const [authUser, setAuthUser] = useState<User | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [isAdmin, setIsAdmin] = useState<boolean>(false);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, async (user: FirebaseUser | null) => {
      if (user) {
        // Map Firebase user to our User interface
        const mappedUser: User = {
          uid: user.uid,
          email: user.email,
          displayName: user.displayName,
          photoURL: user.photoURL,
          isAnonymous: user.isAnonymous
        };

        // Create or update user in Firestore
        const firestoreUser = await UserService.createOrUpdateUser(mappedUser);

        // Sync user role to Realtime Database for security rules
        if (firestoreUser.role) {
          await syncUserRoleToRealtimeDB(user.uid, firestoreUser.role);
        }

        // Set the user with role information
        setAuthUser({
          ...mappedUser,
          role: firestoreUser.role
        });
        setIsAdmin(firestoreUser.role === 'admin' || firestoreUser.role === 'owner');
      } else {
        setAuthUser(null);
        setIsAdmin(false);
      }
      setLoading(false);
    });

    return () => unsubscribe();
  }, []);

  // Auth context value
  const value: AuthContextType = {
    authUser,
    loading,
    isAdmin,
    logout: async () => {/* logout implementation */}
  };
  
  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

Key features of our auth implementation:

  • Role-Based Access Control - User roles (admin, owner, user) determine feature access
  • Persistent Sessions - Firebase persistence for seamless authentication state
  • Security Rules Synchronization - User roles mirrored to Realtime Database for enforcing access rules

Real-time Chat System

The chat functionality is one of the most complex parts of our application. It combines several Firebase services:

  1. Chat Metadata in Firestore:

    • Project information
    • Participant lists
    • Message metadata
  2. Real-time Messages in Realtime Database:

    • Current message content
    • User typing indicators
    • Read receipts

The chat system leverages a custom React Context that encapsulates all chat logic:

// Chat context provides the current state and methods to interact with chat
export function useChat() {
  const context = useContext(ChatContext);
  if (!context) {
    throw new Error('useChat must be used within a ChatProvider');
  }
  return context;
}

// Chat window component utilizes the chat context
export default function ChatWindow(): JSX.Element {
  const { currentProject, loading } = useChat();

  if (!currentProject) {
    return (
      <div className="flex flex-col items-center justify-center h-full bg-gray-900 text-gray-400">
        <p className="text-lg font-medium">Select a project or start a new conversation</p>
        <p className="text-sm mt-2">Your chat conversations will appear here</p>
      </div>
    );
  }

  return (
    <div className="flex flex-col h-full bg-gray-900">
      <ChatHeader />
      <MessageList />
      <TypingIndicator projectId={currentProject.id} />
      <MessageInput />
    </div>
  );
}

Key features of our chat implementation:

  • Project-Based Conversations - Chats organized by projects rather than individual users
  • Real-time Message Delivery - Sub-second message delivery using Firebase Realtime DB
  • Typing Indicators - Show when users are typing messages
  • Read Receipts - Track which messages have been read
  • Message Reactions - React to messages with emojis
  • File Sharing - Upload and share files in conversations

Protected Routes and Layouts

The application enforces access control using Next.js client-side routes with auth checks:

// src/app/(chat)/layout.tsx
export default function ChatLayout({ children }: { children: React.ReactNode }) {
  return (
    <AuthUserProvider>
      <ChatProvider>
        <NotificationProvider>
          <SidebarProvider>
            <InfoSidebarProvider>
              <ChatLayoutContent>{children}</ChatLayoutContent>
            </InfoSidebarProvider>
          </SidebarProvider>
        </NotificationProvider>
      </ChatProvider>
    </AuthUserProvider>
  );
}

function ChatLayoutContent({ children }: { children: React.ReactNode }) {
  const { authUser, loading } = useAuth();
  const router = useRouter();

  // Redirect to login if not authenticated
  useEffect(() => {
    if (!loading && !authUser) {
      router.push('/auth/login');
    }
  }, [authUser, loading, router]);

  if (loading) {
    return <ChatLoading text="Loading ..." />;
  }

  // If not authenticated, useEffect will handle redirection
  if (!authUser) {
    return null;
  }

  return (
    <>
      <ChatSidebarLeft />
      <main className="flex-1 h-full flex flex-col">
        <div className="flex-1 overflow-auto">{children}</div>
      </main>
      <ChatSidebarRight />
    </>
  );
}

Responsive UI with Tailwind CSS

The application uses Tailwind CSS for styling, with a focus on responsive design:

// Homepage layout example showing responsive classes
<div className="mx-auto min-h-screen max-w-screen-xl px-6 py-12 font-sans md:px-12 md:py-20 lg:px-24 lg:py-0">
  <div className="lg:flex lg:justify-between lg:gap-4">
    <header className="lg:sticky lg:top-0 lg:flex lg:max-h-screen lg:w-1/2 lg:flex-col lg:justify-between lg:py-24">
      <div>
        <h1 className="text-4xl font-bold tracking-tight text-slate-200 sm:text-5xl">
          <Link href="/">Md Nasim Sheikh</Link>
        </h1>
        <h2 className="mt-3 text-lg font-medium tracking-tight text-slate-200 sm:text-xl">
          Software Engineer
        </h2>
        <p className="mt-4 max-w-xs leading-normal">
          I build customized software, bring your idea into reality.
        </p>
      </div>
      <Navigation />
      {/* Additional header content */}
    </header>
    
    <main id="content" className="pt-24 lg:w-1/2 lg:py-20">
      <About />
      <Projects />
    </main>
  </div>
</div>

Performance Optimization Techniques

To ensure optimal performance, we've implemented several optimization techniques:

1. Static Site Generation (SSG)

The public-facing content (portfolio, blog posts, projects) is generated at build time using Next.js's static site generation:

// Example of static generation for projects page
export async function getStaticProps() {
  const allProjects = await getAllProjects();
  
  return {
    props: {
      projects: allProjects,
    },
    // Regenerate at most once per day
    revalidate: 86400,
  };
}

2. Code Splitting

Next.js automatically splits code by route, but we've added additional splitting for large components:

// Dynamic import for heavy components
const ChatWindow = dynamic(() => import('@/components/Chat/ChatWindow'), {
  loading: () => <ChatLoading text="Loading chat..." />,
  ssr: false, // Disable server-rendering for chat components
});

3. Image Optimization

We use Next.js Image component for automatic image optimization:

<Image
  src="/images/sf_logo_nobg.png"
  alt="SoftexForge Logo"
  width={200}
  height={80}
  priority
  className="dark:invert"
/>

4. Database Query Optimization

Firebase queries are optimized to fetch only the necessary data:

// Optimized query example - only fetching needed fields
const messagesQuery = query(
  collection(firestore, `projects/${projectId}/messages`),
  orderBy('timestamp', 'desc'),
  limit(50),
  select(['content', 'timestamp', 'senderId', 'type'])
);

Security Considerations

Security is a top priority for our application:

1. Firebase Security Rules

Carefully crafted security rules ensure data access is restricted:

// Simplified Firestore security rules
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read: if request.auth != null;
      allow write: if request.auth.uid == userId || isAdmin();
    }
    
    match /projects/{projectId} {
      allow read: if isProjectMember(projectId);
      allow create: if request.auth != null;
      allow update, delete: if isProjectOwner(projectId) || isAdmin();
      
      match /messages/{messageId} {
        allow read: if isProjectMember(projectId);
        allow create: if isProjectMember(projectId);
        allow update, delete: if request.auth.uid == resource.data.senderId || isAdmin();
      }
    }
    
    function isAdmin() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
    }
    
    function isProjectMember(projectId) {
      return request.auth != null && 
        get(/databases/$(database)/documents/projects/$(projectId)).data.members[request.auth.uid] == true;
    }
    
    function isProjectOwner(projectId) {
      return request.auth != null && 
        get(/databases/$(database)/documents/projects/$(projectId)).data.ownerId == request.auth.uid;
    }
  }
}

2. Authentication Best Practices

  • JWT Tokens - Firebase Auth's secure token approach
  • Secure Session Management - HTTP-only cookies where applicable
  • MFA Ready - Infrastructure to support multi-factor authentication

3. Data Validation

Input validation occurs at multiple levels:

  • Frontend validation with React Hook Form and Zod
  • API route validation with custom middleware
  • Firestore Security Rules for database-level validation

Challenges and Solutions

Challenge 1: Real-time Chat Performance

Problem: Initial implementations of the chat system had performance issues with large message histories.

Solution:

  • Implemented pagination with infinite scrolling for message history
  • Added message batching to reduce Firebase reads
  • Used Firestore for chat metadata and Realtime Database for actual messages to optimize for each use case

Challenge 2: Authentication State Management

Problem: Keeping authentication state consistent across the application, especially with multiple tabs open.

Solution:

  • Leveraged Firebase's built-in auth state persistence
  • Added custom synchronization between tabs using localStorage events
  • Created a robust AuthContext provider with clear loading states

Challenge 3: TypeScript Integration with Firebase

Problem: Ensuring type safety with Firebase's JavaScript-centric APIs.

Solution:

  • Created comprehensive TypeScript interfaces for all Firebase data structures
  • Used generic types for database operations
  • Added runtime type validation with Zod for data coming from Firebase

Future Roadmap

Our application continues to evolve. Here are the key enhancements planned:

Near-term Improvements (Next 3 Months)

  1. Enhanced Analytics - Implement detailed usage metrics to understand user behavior
  2. Offline Support - Add offline capabilities using Service Workers and IndexedDB
  3. Improved CI/CD Pipeline - Automate testing and deployment processes
  4. Accessibility Improvements - Ensure WCAG 2.1 AA compliance

Mid-term Goals (3-6 Months)

  1. API Expansion - Create public APIs for integration with other tools
  2. AI-Powered Features - Add smart suggestions and content analysis
  3. Advanced Search - Implement full-text search across chats and projects
  4. Localization - Add multi-language support

Long-term Vision (6+ Months)

  1. Edge Computing Integration - Deploy critical functions to edge locations for reduced latency
  2. Blockchain Integration - Explore decentralized features for specific use cases
  3. Mobile Application - Develop native mobile apps using React Native
  4. Enterprise Features - Add team management, compliance, and advanced security features

Development Workflow and Tooling

Our development process is designed for efficiency and code quality:

Local Development

# Start the development server
npm run dev

# Lint code
npm run lint

# Test changes against Firestore security rules
npm run deploy-rules -- --dry-run

Deployment Pipeline

  1. Code Review - Pull requests with automated checks
  2. Staging Deployment - Preview environment for testing
  3. Production Deployment - Automated deployment to Vercel

Key Development Tools

  • VS Code with custom extensions for Firebase
  • Firebase Emulator Suite for local testing
  • Chromatic for visual regression testing
  • Lighthouse CI for performance monitoring

Lessons Learned

Throughout development, we've gained valuable insights:

  1. Firebase vs. Traditional Backend

    • Firebase excels at real-time features but requires careful planning for complex data relationships
    • Security rules take time to master but are powerful when properly implemented
  2. Next.js App Router

    • The App Router paradigm required rethinking our approach to routing and layouts
    • Server Components offer performance benefits but introduce complexity in state management
  3. TypeScript Adoption

    • The initial investment in TypeScript paid dividends in reduced bugs and improved developer experience
    • Type inference with Firebase requires extra attention
  4. Performance Optimization

    • Early focus on performance metrics prevented issues later
    • Bundle analysis and code splitting should be part of the regular development workflow

Conclusion

Building this web application has been a journey of balancing modern technologies with pragmatic engineering decisions. The combination of Next.js and Firebase has enabled us to create a feature-rich, performant application with relatively little backend code.

The architecture we've established provides a solid foundation for future growth while maintaining the flexibility to adapt to changing requirements. As we continue to evolve the platform, we remain committed to performance, security, and delivering an exceptional user experience.

For developers embarking on similar projects, we hope this technical deep dive provides valuable insights into the challenges and opportunities of modern web application development.

Application Background

About the Author

Md Nasim Sheikh is a Software Engineer focused on building customized software solutions. With experience in full-stack development and a passion for functional programming, Nasim specializes in creating tailored applications that solve real-world problems.

Connect with him on GitHub or LinkedIn.