Tech Insights

Exploring the frontiers of web development

How to Use the WWW-Authenticate Header in Next.js to Protect Routes with Basic Auth

Want to protect your staging site or sensitive information or route? this is right article for you.

In modern web applications, protecting sensitive routes is crucial. While OAuth and session-based logins are common, sometimes you just need a simple authentication method—especially for internal tools, staging sites, or admin panels.

This is where the WWW-Authenticate header and Basic Authentication come in handy.

In this post, we’ll walk through how to use WWW-Authenticate in Next.js to trigger the browser’s native login popup and secure every route.

What Is WWW-Authenticate?

The WWW-Authenticate response header is part of the HTTP authentication framework. When a browser receives a 401 Unauthorized status along with this header, it automatically prompts the user with a login dialog.

  HTTP/1.1 401 Unauthorized
  WWW-Authenticate: Basic realm="Secure Area"

When Should You Use Basic Auth in Next.js?

•	Securing staging environments
•	Protecting internal dashboards
•	Quick protection without building a full login system
•	When deploying to Vercel or similar platforms

How to Implement WWW-Authenticate in Next.js

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(req: NextRequest) {
	const authHeader = req.headers.get("authorization");

	if (!authHeader) {
		return new NextResponse("Authentication required", {
			status: 401,
			headers: {
				"WWW-Authenticate": 'Basic realm="Secure Area"',
			},
		});
	}

	const [username, password] = atob(authHeader.split(" ")[1] || "").split(":");
	const validUser = process.env.AUTH_USER || "admin";
	const validPass = process.env.AUTH_PASSWORD || "password";

	if (username !== validUser || password !== validPass) {
		return new NextResponse("Invalid credentials", {
			status: 401,
			headers: {
				"WWW-Authenticate": 'Basic realm="Secure Area"',
			},
		});
	}

	return NextResponse.next();
}

export const config = {
	matcher: ["/:path*"], // Apply to all routes
};

Add environment variables

AUTH_USER=admin
AUTH_PASSWORD=secret

Read Article

Next.js Rendering Strategies: A Deep Dive into SSG, SSR, and ISR

Next.js Is Trending — Because of it’s built-in feature like SSG, SSR, or ISR but the question is when to use? One of the biggest reasons for its popularity its powerful rendering strategies:

  • 🔹 SSG (Static Site Generation)
  • 🔹 SSR (Server-Side Rendering)
  • 🔹 ISR (Incremental Static Regeneration)

By Proper understanding of it when to use which strategy can make better performance and user experience. Here's a detailed breakdown with some real-world examples to help choose wisely:

🔹 SSG (Static Site Generation)

When to Use It:

Best For: Content that doesn't change frequently, such as blogs, marketing/landing pages, documentation sites, portfolios, and e-commerce product listings with stable data.

Why It's Great: Pages are pre-rendered into static HTML, CSS, and JavaScript files at build time. This means the server doesn't need to do any work on each request, leading to lightning-fast load times. These static files can be easily served from a CDN, making them highly cacheable, resilient, and cost-effective.

How It Works:

Next.js pre-renders pages using getStaticProps (for data fetching) and getStaticPaths (for dynamic routes). Once built, the HTML files are ready to be served directly to the user.

💡 Real-world Example:

Think of Landing/homepage, these data are not changed frequently. These pages are designed to be extremely fast and reliable. Their content changes infrequently, making SSG the ideal choice for delivering a blazing-fast initial load and excellent SEO. Imagine reading a blog post – you want it to appear instantly, and SSG delivers just that.

🔹 SSR (Server-Side Rendering)

When to Use It:

  • Best For: Highly dynamic content, user-specific data, authentication-gated pages, real-time applications, and situations where data needs to be fresh on every request. Examples include user dashboards, social media feeds, and search results pages.
  • Why It's Great: Content is generated on each request on the server. This allows you to fetch the latest data and personalize the content for the specific user, ensuring they always see up-to-date information. It's perfect when SEO is important for dynamic content.

How It Works:

Next.js uses getServerSideProps to fetch data on every incoming request to the server. The server then renders the HTML with the fetched data and sends it to the client.

💡 Real-world Example:

Consider your LinkedIn feed. When you log in, you expect to see your recent activity, or connections' latest posts. This content is unique to you and changes constantly. SSR ensures that every time you refresh the page, you get the most current and relevant data, providing a truly real-time and personalized experience.

🔹 ISR (Incremental Static Regeneration)

When to Use It:

  • Best For: Pages that benefit from the speed of static generation but also require periodic updates to their content. This is ideal for e-commerce product pages, news articles, content-heavy sites that update regularly, and dynamic but SEO-friendly pages.

  • Why It's Great: ISR offers a powerful hybrid approach. It generates pages at build time (like SSG) but allows them to be revalidated and regenerated in the background after a specified time interval (or on demand) without requiring a full site rebuild. This provides the speed of static content while ensuring reasonable data freshness.

How It Works:

With getStaticProps, you add a revalidate property. When a request comes in for a page that's older than the revalidate time, the stale page is served instantly while Next.js regenerates the new version in the background. Subsequent requests will then receive the fresh version.

💡 Real-world Example:

Think of Amazon product pages or Medium articles. You want these pages to load incredibly fast for a good user experience and SEO. However, product prices, stock levels, or article comments can change frequently. ISR allows Amazon to serve a static, fast version of the product page, and then update it in the background if, for example, the price changes. This way, users get a fast initial load, and the content remains reasonably up-to-date without the need for frequent full site deployments.

Choosing the right rendering strategy is crucial for optimizing Next.js application's performance, user experience, and scalability. By understanding the strengths of SSG, SSR, and ISR, you can make informed decisions that lead to more efficient and powerful web applications.

Read Article

Introduction to Next.js

Introduction to Next.js

Next.js is a powerful React framework that makes building full-stack web applications simple and efficient. Let's explore its key features!

Why Next.js?

Next.js provides several advantages:

  • Server-side rendering (SSR)
  • Static site generation (SSG)
  • API routes
  • File-system based routing
  • Built-in image optimization

Getting Started

# Create a new Next.js project
npx create-next-app@latest my-app
cd my-app

# Start the development server
npm run dev

Basic Routing

Next.js uses a file-system based router. Files in the app directory automatically become routes:

// app/page.tsx - Home page (/)
export default function Home() {
	return <h1>Welcome to Next.js!</h1>;
}

// app/about/page.tsx - About page (/about)
export default function About() {
	return <h1>About Us</h1>;
}

Start building your next project with Next.js for a great developer experience!

Read Article

Getting Started with TypeScript

Getting Started with TypeScript

TypeScript is a powerful superset of JavaScript that adds static typing to the language. Let's explore some basic concepts!

Why TypeScript?

TypeScript offers several benefits:

  • Static typing
  • Better IDE support
  • Enhanced code maintainability
  • Improved team collaboration

Basic Types

// Number
let age: number = 25;

// String
let name: string = "John Doe";

// Boolean
let isStudent: boolean = true;

// Array
let numbers: number[] = [1, 2, 3, 4, 5];

Interfaces

Interfaces are one of TypeScript's most powerful features:

interface User {
	name: string;
	age: number;
	email?: string; // Optional property
}

const user: User = {
	name: "John Doe",
	age: 25,
};

Start using TypeScript in your next project to write more maintainable code!

Read Article

Software Architecture Patterns and Design Principles

Software Architecture Patterns

Understanding software architecture patterns and design principles is crucial for building scalable and maintainable applications. Let's explore some key patterns and principles.

Clean Architecture

Clean Architecture promotes separation of concerns and dependency rules:

// Domain Layer
interface User {
	id: string;
	name: string;
	email: string;
}

// Use Case Layer
class CreateUserUseCase {
	constructor(private userRepository: UserRepository) {}

	async execute(userData: User): Promise<User> {
		return this.userRepository.create(userData);
	}
}

// Repository Interface
interface UserRepository {
	create(user: User): Promise<User>;
	findById(id: string): Promise<User | null>;
}

SOLID Principles

Single Responsibility

A class should have only one reason to change:

// Bad
class UserService {
	saveUser() {
		/* ... */
	}
	sendEmail() {
		/* ... */
	}
	generateReport() {
		/* ... */
	}
}

// Good
class UserService {
	saveUser() {
		/* ... */
	}
}

class EmailService {
	sendEmail() {
		/* ... */
	}
}

class ReportService {
	generateReport() {
		/* ... */
	}
}

Microservices Architecture

Break down applications into small, independent services:

// User Service
class UserService {
	async createUser(user: User): Promise<User> {
		// Handle user creation
		return user;
	}
}

// Order Service
class OrderService {
	async createOrder(order: Order): Promise<Order> {
		// Handle order creation
		return order;
	}
}

Event-Driven Architecture

Use events for loose coupling between components:

interface EventBus {
	publish(event: Event): void;
	subscribe(eventType: string, handler: (event: Event) => void): void;
}

class OrderCreatedEvent implements Event {
	constructor(public readonly orderId: string) {}
}

class NotificationHandler {
	handle(event: OrderCreatedEvent) {
		// Send notification
	}
}

Repository Pattern

Abstract data access logic:

interface Repository<T> {
	findById(id: string): Promise<T | null>;
	findAll(): Promise<T[]>;
	create(entity: T): Promise<T>;
	update(entity: T): Promise<T>;
	delete(id: string): Promise<void>;
}

class UserRepository implements Repository<User> {
	// Implementation
}

Best Practices

  1. Follow SOLID principles
  2. Use dependency injection
  3. Keep components loosely coupled
  4. Write testable code
  5. Document architecture decisions
  6. Consider scalability from the start

Common Anti-patterns to Avoid

  1. God Objects
  2. Tight Coupling
  3. Premature Optimization
  4. Magic Strings/Numbers
  5. Duplicate Code

Conclusion

Choosing the right architecture patterns and following solid design principles is crucial for building maintainable and scalable applications. These patterns and principles provide a foundation for writing clean, testable, and maintainable code.

Read Article

Web Accessibility Guide: Building Inclusive Applications

Web Accessibility Guide

Building accessible web applications is crucial for ensuring that everyone, regardless of their abilities, can use your website effectively. Let's explore key accessibility principles and implementation techniques.

ARIA Roles and Attributes

ARIA helps make web content more accessible to people using screen readers:

<button role="button" aria-label="Close dialog" aria-pressed="false"></button>

Semantic HTML

Use semantic HTML elements to provide meaning and structure:

<!-- Bad -->
<div class="button" onclick="submit()">Submit</div>

<!-- Good -->
<button type="submit">Submit</button>

Keyboard Navigation

Ensure all interactive elements are keyboard accessible:

function handleKeyPress(event) {
	if (event.key === "Enter" || event.key === " ") {
		event.preventDefault();
		// Handle activation
	}
}

Focus Management

Implement proper focus management for interactive elements:

// Trap focus in modal
function trapFocus(element) {
	const focusableElements = element.querySelectorAll(
		'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
	);
	const firstFocusable = focusableElements[0];
	const lastFocusable = focusableElements[focusableElements.length - 1];

	// Implement focus trap logic
}

Color Contrast

Ensure sufficient color contrast for text readability:

/* Good contrast ratio */
.text-content {
	color: #333333;
	background-color: #ffffff;
}

/* High contrast for important elements */
.alert {
	color: #ffffff;
	background-color: #d42626;
}

Screen Reader Considerations

Provide alternative text for images and meaningful descriptions:

<img src="profile.jpg" alt="John Doe smiling in a professional headshot" />

<figure>
	<img src="chart.png" alt="" aria-describedby="chart-desc" />
	<figcaption id="chart-desc">
		Bar chart showing sales growth over Q1 2024
	</figcaption>
</figure>

Best Practices

  1. Use proper heading hierarchy (h1-h6)
  2. Provide sufficient color contrast
  3. Implement keyboard navigation
  4. Add descriptive alt text for images
  5. Use ARIA labels when necessary
  6. Test with screen readers
  7. Support text resizing

Testing Tools

  • WAVE Web Accessibility Evaluation Tool
  • axe DevTools
  • Lighthouse Accessibility Audit
  • Screen readers (NVDA, VoiceOver)

Conclusion

Accessibility is not just about compliance; it's about creating an inclusive web that everyone can use. By following these guidelines and best practices, you can make your web applications more accessible to all users, regardless of their abilities.

Read Article

Next.js Fundamentals: Building Modern Web Applications

Next.js Fundamentals

Next.js is a powerful React framework that enables you to build production-ready web applications. Let's explore its core features and best practices.

Server-Side Rendering (SSR)

Next.js provides built-in SSR capabilities:

export async function getServerSideProps(context) {
	const data = await fetchData();
	return {
		props: { data },
	};
}

Static Site Generation (SSG)

Pre-render pages at build time:

export async function getStaticProps() {
	const posts = await getPosts();
	return {
		props: { posts },
		revalidate: 60, // Optional ISR
	};
}

File-Based Routing

Next.js uses a file-system based router:

pages/
  index.js        // -> /
  about.js        // -> /about
  blog/
    [slug].js     // -> /blog/:slug
    index.js      // -> /blog

API Routes

Create API endpoints within your Next.js app:

// pages/api/users.js
export default function handler(req, res) {
	if (req.method === "GET") {
		res.status(200).json({ users: [] });
	}
}

Image Optimization

Use the built-in Image component for optimal performance:

import Image from "next/image";

function MyComponent() {
	return (
		<Image src="/profile.jpg" alt="Profile" width={500} height={300} priority />
	);
}

Data Fetching Patterns

Client-Side Data Fetching

import useSWR from "swr";

function Profile() {
	const { data, error } = useSWR("/api/user", fetcher);

	if (error) return <div>Error loading</div>;
	if (!data) return <div>Loading...</div>;
	return <div>Hello {data.name}!</div>;
}

Performance Optimization

  1. Use dynamic imports for code splitting
  2. Implement proper caching strategies
  3. Optimize images and fonts
  4. Leverage Edge Functions for faster response

Best Practices

  1. Follow the recommended project structure
  2. Use appropriate data fetching methods
  3. Implement proper error boundaries
  4. Optimize for Core Web Vitals
  5. Use TypeScript for better type safety

Deployment

Next.js applications can be easily deployed to various platforms:

  • Vercel (Zero configuration)
  • AWS
  • Google Cloud
  • Custom server

Conclusion

Next.js provides a robust framework for building modern web applications with features like SSR, SSG, and automatic image optimization. Understanding these fundamentals will help you create performant and scalable applications.

Read Article

Understanding React Hooks: A Comprehensive Guide

Understanding React Hooks

React Hooks have revolutionized how we write React components. In this comprehensive guide, we'll explore the most important hooks and how to use them effectively.

useState: Managing Component State

The useState hook is the foundation of state management in functional components. Here's how to use it:

const [count, setCount] = useState(0);

Common Use Cases

  • Form inputs
  • Toggle states
  • Counter implementations
  • UI state management

useEffect: Handling Side Effects

The useEffect hook helps manage side effects in your components:

useEffect(() => {
	document.title = `Count: ${count}`;
}, [count]);

Key Points

  • Runs after every render
  • Can specify dependencies
  • Cleanup function for unmounting

useContext: Managing Global State

Context provides a way to pass data through the component tree without prop drilling:

const theme = useContext(ThemeContext);

Custom Hooks

Create reusable logic by building custom hooks:

function useWindowSize() {
	const [size, setSize] = useState([window.innerWidth, window.innerHeight]);
	// Implementation
	return size;
}

Best Practices

  1. Always name hooks with 'use' prefix
  2. Only call hooks at the top level
  3. Use multiple hooks for different concerns
  4. Keep hooks simple and focused

Conclusion

React Hooks provide a more elegant way to reuse stateful logic and manage component lifecycle. By understanding these fundamental hooks, you can write more maintainable and efficient React applications."

Read Article

Modern JavaScript Features Every Developer Should Know

Modern JavaScript Features

JavaScript has evolved significantly over the years. Let's explore some essential modern features that can make your code more elegant and maintainable.

Arrow Functions

Arrow functions provide a concise syntax for writing function expressions:

// Traditional function
function add(a, b) {
	return a + b;
}

// Arrow function
const add = (a, b) => a + b;

Destructuring

Destructuring makes it easier to extract values from objects and arrays:

// Object destructuring
const { name, age } = person;

// Array destructuring
const [first, second] = numbers;

Spread and Rest Operators

These operators make working with arrays and objects more convenient:

// Spread operator
const newArray = [...oldArray, newItem];
const newObject = { ...oldObject, newProperty: value };

// Rest operator
const [first, ...rest] = numbers;
const { id, ...userDetails } = user;

Optional Chaining

Safely access nested object properties without causing errors:

const userName = user?.profile?.name;

Nullish Coalescing

Provide fallback values for null or undefined:

const value = someValue ?? defaultValue;

Template Literals

Write multi-line strings and embed expressions:

const message = `Hello ${name},
  Welcome to our platform!`;

Async/Await

Write asynchronous code that looks synchronous:

async function fetchData() {
	try {
		const response = await fetch(url);
		const data = await response.json();
		return data;
	} catch (error) {
		console.error(error);
	}
}

Best Practices

  1. Use const by default, let when needed
  2. Prefer arrow functions for callbacks
  3. Use destructuring for cleaner code
  4. Leverage template literals for string interpolation
  5. Always handle promises with try/catch

Conclusion

Mastering these modern JavaScript features will help you write more concise, readable, and maintainable code. Stay updated with the latest features to keep your skills sharp and your code modern.

Read Article

TypeScript Essentials: A Guide to Static Typing in JavaScript

TypeScript Essentials

TypeScript adds static typing to JavaScript, making your code more robust and maintainable. Let's explore the key concepts and features.

Basic Types

TypeScript provides several basic types:

// Basic type annotations
let name: string = "John";
let age: number = 30;
let isActive: boolean = true;
let numbers: number[] = [1, 2, 3];
let tuple: [string, number] = ["hello", 42];

Interfaces

Interfaces define contracts in your code:

interface User {
	id: number;
	name: string;
	email: string;
	age?: number; // Optional property
}

function createUser(user: User): User {
	return user;
}

Type Aliases

Create custom types with type aliases:

type Point = {
	x: number;
	y: number;
};

type ID = string | number;

Generics

Write reusable, type-safe code with generics:

function identity<T>(arg: T): T {
	return arg;
}

const num = identity<number>(42);
const str = identity<string>("hello");

Enums

Define a set of named constants:

enum Direction {
	Up = "UP",
	Down = "DOWN",
	Left = "LEFT",
	Right = "RIGHT",
}

let movement: Direction = Direction.Up;

Type Assertions

Tell TypeScript about the specific type of a value:

let someValue: unknown = "hello world";
let strLength: number = (someValue as string).length;

Best Practices

  1. Always define proper types for variables and functions
  2. Use interfaces for object shapes
  3. Leverage type inference when possible
  4. Avoid using any type unless necessary
  5. Use union types for flexibility

Advanced Features

Mapped Types

type Readonly<T> = {
	readonly [P in keyof T]: T[P];
};

type Optional<T> = {
	[P in keyof T]?: T[P];
};

Utility Types

// Built-in utility types
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
type PickedUser = Pick<User, "id" | "name">;

Conclusion

TypeScript's static typing system helps catch errors early, improves code maintainability, and provides better tooling support. Understanding these fundamentals will help you write more reliable and scalable applications.

Read Article