TypeScript and SSR/SSG Best Practices for Ecommerce Applications
September 22, 2025
TypeScript and SSR/SSG Best Practices for Ecommerce Applications
In modern ecommerce development, TypeScript and server-side rendering strategies are essential for building scalable, maintainable, and SEO-friendly applications. This comprehensive guide covers TypeScript best practices and SSR/SSG implementation strategies with real-world examples from ecommerce projects.
1. TypeScript Fundamentals
Why TypeScript is Important in Large Ecommerce Projects
TypeScript is crucial in large ecommerce projects because:
-
Type safety: Reduces runtime errors by checking data types at compile time, especially with complex data like products, cart, and user information.
-
Easy refactoring: Types and interfaces help safely change code when adding new features like product filters or payment methods.
-
Better developer experience: Autocomplete and type checking in IDE speeds up development and reduces bugs significantly.
-
Support for large teams: Ensures consistency when many developers work together on the same codebase.
Specific example: In an ecommerce project, I used TypeScript to define types for API response of product list, avoiding errors when accessing non-existent properties like product.price.
interface Product {
id: string;
name: string;
price: number;
category: string;
}
interface ApiResponse {
products: Product[];
total: number;
}
const fetchProducts = async (): Promise<ApiResponse> => {
const response = await fetch("/api/products");
return response.json();
};Application: TypeScript helps detect errors if API returns missing price, ensures ProductList component displays correct data, and provides better IntelliSense support.
Defining Types for Complex React Component Props
To define types for complex props in React components, I use interface or type in TypeScript to describe the props data structure. For example, for a ProductList component that receives product data:
interface Product {
id: string;
name: string;
price: number;
image?: string; // Optional prop
}
interface ProductListProps {
products: Product[];
onSelectProduct: (id: string) => void;
isLoading: boolean;
}
const ProductList: React.FC<ProductListProps> = ({
products,
onSelectProduct,
isLoading
}) => {
if (isLoading) return <div>Loading...</div>;
return (
<div>
{products.map((product) => (
<div key={product.id} onClick={() => onSelectProduct(product.id)}>
{product.name} - ${product.price}
</div>
))}
</div>
);
};
export default ProductList;Implementation:
- Define
interface Productfor each product object - Define
interface ProductListPropsfor component props, includingproductsarray,onSelectProductcallback, andisLoadingstate - Use
React.FCso TypeScript automatically infers type forchildren(if any)
Application in ecommerce: Ensures ProductList receives correct data, detects errors if missing id or price, and supports autocomplete when coding.
Common TypeScript Errors and Solutions
In ecommerce projects, I encountered TypeScript errors when fetching data from API where response didn’t match defined interface. API returned price as string (like "19.99") instead of number as in Product interface.
Error:
interface Product {
id: string;
name: string;
price: number;
}
const product: Product = await (await fetch("/api/product/123")).json();
// Error: 'price' is string, not numberSolution:
- Update interface and add parsing step in fetch function
- Use type assertion or mapper to convert data
interface Product {
id: string;
name: string;
price: number;
}
const fetchProduct = async (id: string): Promise<Product> => {
const response = await fetch(`/api/product/${id}`);
const data = await response.json();
return {
...data,
price: parseFloat(data.price), // Convert string to number
};
};Result: Error resolved, and TypeScript no longer reports errors. I also added error checking (like if (!response.ok) throw new Error('Fetch failed')) to handle API failures.
Lesson: Always check actual API response and add parsing logic to match interface definitions.
Interface vs Type: When to Use Which
Interface:
- Used to define object structure (like props, API response)
- Supports
declaration merging(can extend interface elsewhere) - Usually used for React props or objects with fixed structure
- Example:
interface Product {
id: string;
name: string;
}
interface Product {
price: number; // Declaration merging
}Type:
- More flexible, supports union types, intersection types, and primitive types
- Doesn’t support declaration merging
- Used for complex cases like union types or type alias
- Example:
type ProductId = string | number;
type Product = {
id: ProductId;
name: string;
} & { price: number }; // IntersectionWhen to use:
- Interface: Use for component props, API response in ecommerce (like
Product,CartItem) because easy to extend - Type: Use for complex logic like union types (example:
PaymentMethod = 'card' | 'paypal'), or when need to combine multiple types
Ecommerce example: I use interface for ProductProps in ProductCard and type for PaymentStatus ('pending' | 'success' | 'failed') in checkout page.
TypeScript with React Query and State Management
I’ll describe how to use TypeScript with React Query (popular in ecommerce for data fetching):
- Define types for query keys, response, and error
- Use useQuery with type generic to ensure type safety
import { useQuery } from "@tanstack/react-query";
interface Product {
id: string;
name: string;
price: number;
}
interface ProductResponse {
products: Product[];
total: number;
}
const fetchProducts = async (): Promise<ProductResponse> => {
const response = await fetch("/api/products");
if (!response.ok) throw new Error("Fetch failed");
return response.json();
};
const useProducts = () => {
return useQuery<ProductResponse, Error>({
queryKey: ["products"],
queryFn: fetchProducts,
});
};
export default useProducts;
// Usage: const { data, isLoading, error } = useProducts();With Redux:
- Define types for actions, state, and reducer
- Use
@reduxjs/toolkitto reduce boilerplate and support TypeScript
Application in ecommerce: React Query with TypeScript helps safely fetch product list, ensures data.products has correct type, avoids errors when rendering ProductList.
2. Server-Side Rendering (SSR) and Static Site Generation (SSG)
Understanding SSR, SSG, and CSR
SSR (Server-Side Rendering):
- Renders HTML on server for each request, sends to client
- Advantages: Good for SEO, fast initial load
- Disadvantages: Uses server resources, slower than SSG for static data
SSG (Static Site Generation):
- Renders HTML at build time, serves static files
- Advantages: Fast, supports caching, reduces server load
- Disadvantages: Not suitable for constantly changing data
CSR (Client-Side Rendering):
- Renders HTML on client using JavaScript
- Advantages: Dynamic interface, no need for server rendering
- Disadvantages: Poor SEO, slow initial load
When to choose SSR in ecommerce:
- Use SSR for pages that need SEO and dynamic data, like product detail pages (
/products/[id]) to show latest price, description - Example: Product page needs SEO for Google indexing and price can change frequently
Ecommerce example: I use SSR for product detail pages to ensure SEO and real-time data, SSG for static category pages, and CSR for cart (only needs client-side rendering).
Optimizing SEO for Product Pages with SSR
To optimize SEO with SSR in Next.js:
- Use
getServerSideProps: Fetch product data and create complete HTML on server - Add meta tags: Use
next/headto add title, description, and Open Graph tags - Optimize HTML structure: Ensure main content (name, price, description) is available in HTML
import Head from 'next/head';
interface Product {
id: string;
name: string;
price: number;
description: string;
}
export async function getServerSideProps({ params }: { params: { id: string } }) {
const response = await fetch(`/api/products/${params.id}`);
const product = await response.json();
return { props: { product } };
}
const ProductPage: React.FC<{ product: Product }> = ({ product }) => {
return (
<>
<Head>
<title>{product.name} - My Ecommerce</title>
<meta name="description" content={product.description} />
<meta property="og:title" content={product.name} />
<meta property="og:description" content={product.description} />
</Head>
<div>
<h1>{product.name}</h1>
<p>${product.price}</p>
<p>{product.description}</p>
</div>
</>
);
};
export default ProductPage;Application: HTML contains complete content helps Googlebot index name, price, description, improves search ranking significantly.
Incremental Static Regeneration (ISR) for Product Lists
ISR (Incremental Static Regeneration) in Next.js allows creating static content at build time but updates periodically or on demand without rebuilding entire app.
How it works:
- In
getStaticProps, addrevalidateproperty (seconds) to specify content regeneration time - If there’s a request after
revalidatetime, Next.js recreates page in background and serves new version
Ecommerce example:
- Use ISR for product list pages (
/products/[category]) because categories don’t change frequently (every few hours)
interface Product {
id: string;
name: string;
}
export async function getStaticPaths() {
const categories = ['electronics', 'clothing'];
const paths = categories.map((category) => ({ params: { category } }));
return { paths, fallback: 'blocking' };
}
export async function getStaticProps({ params }: { params: { category: string } }) {
const response = await fetch(`/api/products?category=${params.category}`);
const products = await response.json();
return {
props: { products },
revalidate: 3600, // Regenerate after 1 hour
};
}
const CategoryPage: React.FC<{ products: Product[] }> = ({ products }) => {
return (
<div>
{products.map((product) => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
};
export default CategoryPage;Application: ISR ensures category pages load fast (from static files) but still update when new products arrive, saves server resources significantly.
Performance Issues with SSR and Solutions
Situation: In an ecommerce project, product detail page used SSR (getServerSideProps) to fetch data from API. When traffic increased (like flash sale), server was overloaded because every request called API and rendered HTML.
Performance issues:
- API called continuously, causing delays (high TTFB)
- Server consumed resources to render HTML for each request
How to fix:
-
Switch to ISR: If product data doesn’t change frequently, use
getStaticPropswithrevalidateto create static content, reduce server load -
Caching: Use CDN (like Cloudflare) or Redis to cache API response, avoid repeated API calls
-
Client-side fetching for dynamic data: Use React Query to fetch real-time data (like promotional prices) on client, combine with SSR for static content
-
Optimize API: Work with backend team to optimize database queries (like using index or batch requests)
Result: In the project, I switched product page to ISR with revalidate: 600 (10 minutes) and used React Query for promotional prices. TTFB reduced 50%, and server handled double the traffic.
Handling Client-Side State in Next.js with SSR
To handle client-side state in Next.js app with SSR:
-
Combine SSR and CSR:
- Use
getServerSidePropsto render initial data (like product list) - Use
useStateoruseReducerto manage client-side state (like filters, cart)
- Use
-
Hydration: Next.js automatically hydrates state from SSR to client, but need to ensure client-side state doesn’t conflict
-
React Query/Context API: Manage dynamic state (like cart) with React Query or Context API
Example: Product list page with client-side filter:
import { useState } from 'react';
interface Product {
id: string;
name: string;
price: number;
}
export async function getServerSideProps() {
const response = await fetch('/api/products');
const products = await response.json();
return { props: { initialProducts: products } };
}
const ProductsPage: React.FC<{ initialProducts: Product[] }> = ({ initialProducts }) => {
const [filter, setFilter] = useState('');
const filteredProducts = initialProducts.filter((product) =>
product.name.toLowerCase().includes(filter.toLowerCase())
);
return (
<div>
<input
type="text"
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="Filter products"
/>
{filteredProducts.map((product) => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
};
export default ProductsPage;Application:
getServerSidePropsprovides initial data, ensures SEOuseStatemanages filter on client, no need to call server again- If need to fetch dynamic data (like price updates), use React Query to optimize
3. Best Practices and Performance Optimization
TypeScript Configuration for Large Projects
For large ecommerce projects, I recommend these TypeScript configurations:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@/components/*": ["src/components/*"],
"@/utils/*": ["src/utils/*"]
}
}
}Benefits:
- Strict type checking prevents runtime errors
- Path mapping makes imports cleaner and more maintainable
- Better IDE support with autocomplete and refactoring
SSR/SSG Performance Monitoring
To monitor and optimize SSR/SSG performance:
- Measure Core Web Vitals: Use tools like Lighthouse or Web Vitals to track LCP, FID, CLS
- Monitor TTFB: Track Time to First Byte to identify server-side bottlenecks
- Use Next.js Analytics: Implement
@next/bundle-analyzerto identify large bundles - Cache Strategy: Implement proper caching headers and CDN configuration
Example monitoring setup:
// pages/_app.tsx
export function reportWebVitals(metric: any) {
console.log(metric);
// Send to analytics service
}Conclusion
TypeScript and SSR/SSG are powerful tools for building modern ecommerce applications. By implementing proper type safety, choosing the right rendering strategy, and following performance best practices, you can create applications that are:
- Type-safe and maintainable
- SEO-optimized for better search rankings
- Performance-optimized for better user experience
- Scalable for growing businesses
Remember to:
- Start with TypeScript interfaces for your core data models
- Choose SSR for dynamic, SEO-critical pages
- Use SSG/ISR for static or semi-static content
- Monitor performance and optimize based on real metrics
- Test your implementation with real traffic patterns
The combination of TypeScript’s type safety and Next.js’s rendering strategies provides a solid foundation for building robust ecommerce applications that can scale with your business needs.
Have you implemented TypeScript and SSR/SSG in your ecommerce projects? Share your experiences and challenges in the comments below!