LogoBoring Template

Utility Functions

A collection of utility functions for common tasks like styling, routing, encryption, and string manipulation.

This document provides an overview of utility functions used throughout the application for common tasks like styling, routing, encryption, and string manipulation.

Styling Utilities

cn(...inputs)

Combines Tailwind classes using clsx and tailwind-merge for conflict resolution.

lib/utils.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Example usage:

import { cn } from "@/lib/utils";
 
// In your component
<div className={cn("text-red-500", "bg-blue-500", className)}>Content</div>;

Routing & Navigation

isRouteActive()

Checks if a route is active based on the current path and specified depth.

lib/routing.ts
export function isRouteActive(
  targetLink: string,
  currentRoute: string,
  depth: number = 1
): boolean {
  if (!targetLink || !currentRoute) return false;
 
  // Remove query params and hash
  const normalizedTarget = targetLink.split("?")[0].split("#")[0];
  const normalizedCurrent = currentRoute.split("?")[0].split("#")[0];
 
  // Split into segments
  const targetSegments = normalizedTarget.split("/").filter(Boolean);
  const currentSegments = normalizedCurrent.split("/").filter(Boolean);
 
  // Compare segments up to the specified depth
  for (let i = 0; i < depth; i++) {
    if (targetSegments[i] !== currentSegments[i]) {
      return false;
    }
  }
 
  return true;
}

Parameters

  1. targetLink - The link to check against
  2. currentRoute - The current route
  3. depth - How far down segments should be matched

Example usage:

import { isRouteActive } from "@/lib/routing";
import { usePathname } from "next/navigation";
 
// In your component
const pathname = usePathname();
const isActive = isRouteActive("/dashboard/settings", pathname, 2);

pagePathname(metaUrl)

Extracts the page pathname based on file location in the app directory.

lib/routing.ts
export function pagePathname(metaUrl: string): string {
  // Extract pathname from metaUrl (import.meta.url in a Page component)
  const fileUrl = new URL(metaUrl);
  const pathname = fileUrl.pathname
    .replace(/^.*\/app/, "") // Remove everything up to /app
    .replace(/\/(page|route)\.(js|jsx|ts|tsx)$/, "") // Remove page/route file name
    .replace(/\/\(.*\)/, "") // Remove route groups like /(auth)
    .replace(/\/@.*\//, "/"); // Remove intercepted routes like /@modal/
 
  return pathname || "/";
}

Example usage:

import { pagePathname } from "@/lib/routing";
 
// In your page component
export default function DashboardPage() {
  const pathname = pagePathname(import.meta.url);
  // Returns "/dashboard" if the file is app/dashboard/page.tsx
 
  return <div>Current page: {pathname}</div>;
}

Encryption & Security

Encryption functions require an ENCRYPTION_KEY environment variable to be set.

encrypt(text)

Encrypts text using AES-256-CTR algorithm.

lib/encryption.ts
import crypto from "crypto";
 
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY;
const ALGORITHM = "aes-256-ctr";
const IV_LENGTH = 16;
 
export function encrypt(text: string): string {
  if (!ENCRYPTION_KEY) {
    throw new Error("ENCRYPTION_KEY environment variable is not set");
  }
 
  const iv = crypto.randomBytes(IV_LENGTH);
  const key = crypto.createHash("sha256").update(ENCRYPTION_KEY).digest();
  const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
 
  const encrypted = Buffer.concat([
    cipher.update(text, "utf8"),
    cipher.final(),
  ]);
 
  return `${iv.toString("hex")}:${encrypted.toString("hex")}`;
}

Example usage:

import { encrypt } from "@/lib/encryption";
 
const encrypted = encrypt("sensitive data");
// Returns something like "3a45e1f8c7d9b2a0:7890abcdef1234567890abcdef123456"

decrypt(encryptedText)

Decrypts previously encrypted text.

lib/encryption.ts
export function decrypt(encryptedText: string): string {
  if (!ENCRYPTION_KEY) {
    throw new Error("ENCRYPTION_KEY environment variable is not set");
  }
 
  const [ivHex, encryptedHex] = encryptedText.split(":");
  const iv = Buffer.from(ivHex, "hex");
  const encrypted = Buffer.from(encryptedHex, "hex");
  const key = crypto.createHash("sha256").update(ENCRYPTION_KEY).digest();
  const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
 
  const decrypted = Buffer.concat([
    decipher.update(encrypted),
    decipher.final(),
  ]);
 
  return decrypted.toString("utf8");
}

Example usage:

import { decrypt } from "@/lib/encryption";
 
const decrypted = decrypt(encrypted);
// Returns "sensitive data"

String Manipulation

slugify(text)

Converts text into URL-friendly slugs.

lib/strings.ts
export function slugify(text: string): string {
  return text
    .toString()
    .toLowerCase()
    .trim()
    .replace(/\s+/g, "-") // Replace spaces with -
    .replace(/&/g, "-and-") // Replace & with 'and'
    .replace(/[^\w\-]+/g, "") // Remove all non-word characters
    .replace(/\-\-+/g, "-") // Replace multiple - with single -
    .replace(/^-+/, "") // Trim - from start of text
    .replace(/-+$/, ""); // Trim - from end of text
}

Example usage:

import { slugify } from "@/lib/strings";
 
slugify("Hello World!"); // returns "hello-world"
slugify("User & Company"); // returns "user-and-company"

capitalizeFirstLetter(str)

Capitalizes the first letter of a string.

lib/strings.ts
export function capitalizeFirstLetter(str: string): string {
  if (!str || typeof str !== "string") return "";
  return str.charAt(0).toUpperCase() + str.slice(1);
}

Example usage:

import { capitalizeFirstLetter } from "@/lib/strings";
 
capitalizeFirstLetter("hello"); // returns "Hello"
capitalizeFirstLetter("world of code"); // returns "World of code"

Validation

Regular Expressions

Common regular expressions used for validation:

lib/validation.ts
export const nameRegex = /^[a-zA-Z0-9\s\-]+$/;
export const slugRegex = /^[a-z0-9\-]+$/;
export const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export const passwordRegex =
  /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{8,}$/;

Usage examples:

  1. nameRegex - Validates names (letters, numbers, spaces, hyphens)
  2. slugRegex - Validates URL-friendly slugs
  3. emailRegex - Validates email addresses
  4. passwordRegex - Validates strong passwords

transformErrorToDetails(error)

Transforms Zod validation errors into readable strings.

lib/validation.ts
import { ZodError } from "zod";
 
export function transformErrorToDetails(error: unknown): string[] {
  if (error instanceof ZodError) {
    return error.errors.map(
      (issue) => `${issue.path.join(".")}: ${issue.message}`
    );
  }
 
  if (error instanceof Error) {
    return [error.message];
  }
 
  return ["An unknown error occurred"];
}

Example usage:

import { transformErrorToDetails } from "@/lib/validation";
import { z } from "zod";
 
const schema = z.object({
  name: z.string().min(3),
  email: z.string().email(),
});
 
try {
  schema.parse({ name: "Jo", email: "not-an-email" });
} catch (error) {
  const details = transformErrorToDetails(error);
  // Returns ["name: String must contain at least 3 character(s)", "email: Invalid email"]
}

Image & URL Utilities

placeholderImageUrl(options)

Generates placeholder image URLs with customizable colors and text.

lib/images.ts
interface PlaceholderOptions {
  width?: number;
  height?: number;
  text?: string;
  backgroundColor?: string;
  textColor?: string;
}
 
export function placeholderImageUrl(options: PlaceholderOptions = {}): string {
  const {
    width = 300,
    height = 200,
    text = `${width}×${height}`,
    backgroundColor = "cccccc",
    textColor = "666666",
  } = options;
 
  // Using placeholder.com service
  return `https://via.placeholder.com/${width}x${height}/${backgroundColor}/${textColor}?text=${encodeURIComponent(
    text
  )}`;
}

Example usage:

import { placeholderImageUrl } from "@/lib/images";
 
// Basic usage
const url1 = placeholderImageUrl();
// Returns "https://via.placeholder.com/300x200/cccccc/666666?text=300×200"
 
// Custom options
const url2 = placeholderImageUrl({
  width: 500,
  height: 300,
  text: "Loading...",
  backgroundColor: "f5f5f5",
  textColor: "333333",
});
// Returns "https://via.placeholder.com/500x300/f5f5f5/333333?text=Loading..."

absoluteUrl(path)

Converts relative paths to absolute URLs using the app's base URL.

lib/urls.ts
export function absoluteUrl(path: string): string {
  // Use environment variable or default to localhost in development
  const baseUrl =
    process.env.NEXT_PUBLIC_APP_URL ||
    (process.env.VERCEL_URL
      ? `https://${process.env.VERCEL_URL}`
      : "http://localhost:3000");
 
  // Ensure path starts with slash
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
 
  return `${baseUrl}${normalizedPath}`;
}

Example usage:

import { absoluteUrl } from "@/lib/urls";
 
// With environment variable NEXT_PUBLIC_APP_URL=https://example.com
absoluteUrl("/dashboard"); // Returns "https://example.com/dashboard"
absoluteUrl("api/auth"); // Returns "https://example.com/api/auth"

Key Features

Modular Design

🧩 Small, focused utilities that do one thing well

Type Safety

🔄 Full TypeScript support with proper type definitions

Error Handling

🛡️ Robust error handling with meaningful messages

Zero Dependencies

📦 Minimal external dependencies for core utilities

Additional Utilities

formatDate(date)

Formats dates in a user-friendly format.

lib/dates.ts
export function formatDate(
  date: Date | string | number,
  options: Intl.DateTimeFormatOptions = {
    month: "short",
    day: "numeric",
    year: "numeric",
  }
): string {
  const d = new Date(date);
  return new Intl.DateTimeFormat("en-US", options).format(d);
}

Example usage:

import { formatDate } from "@/lib/dates";
 
formatDate(new Date("2023-05-15"));
// Returns "May 15, 2023"
 
formatDate("2023-05-15", { dateStyle: "full" });
// Returns "Monday, May 15, 2023"

debounce(func, wait)

Debounces a function to limit how often it can be called.

lib/performance.ts
export function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: NodeJS.Timeout | null = null;
 
  return function (...args: Parameters<T>) {
    if (timeout) clearTimeout(timeout);
 
    timeout = setTimeout(() => {
      func(...args);
    }, wait);
  };
}

Example usage:

import { debounce } from "@/lib/performance"
 
// Debounce search input to prevent excessive API calls
const debouncedSearch = debounce((term: string) => {
  // Call API with search term
  fetchSearchResults(term)
}, 300)
 
// In your component
<input
  type="text"
  onChange={(e) => debouncedSearch(e.target.value)}
  placeholder="Search..."
/>

Usage Guidelines

All utility functions are located in the lib/ directory and can be imported directly from their respective files or through the barrel exports.

For better code organization and bundle size optimization:

  1. Import specific utilities directly from their source files when possible
  2. Use the barrel exports for convenience when importing multiple utilities
  3. Keep utility functions pure and side-effect free
  4. Write comprehensive tests for utility functions

Example of importing:

// Specific import (preferred for tree-shaking)
import { cn } from "@/lib/utils";
import { slugify } from "@/lib/strings";
 
// Barrel import (convenient but less optimized)
import { cn, slugify } from "@/lib";