Introduction

Modern web development often presents a critical architectural choice: client-side rendering (CSR) or server-side rendering (SSR).

CSR frameworks like React excel at building highly interactive, component-based user interfaces. However, for use cases such as authentication flows, admin dashboards, and security-sensitive applications, server-side rendering offers clear advantages, improved performance, better SEO, and stronger control over data exposure.

The challenge is that moving to SSR often means sacrificing the component-driven development model that makes React so productive. Developers frequently end up writing repetitive templates, manually managing CSS classes, and duplicating UI logic between backend-rendered views and frontend components.

This article introduces a practical solution: a React-like component system built entirely on the server, using NestJS, Handlebars, and Tailwind CSS. 

What This Guide Will Teach You

By the end of this guide, you’ll understand how to design and implement a React-like component system for server-side rendering using NestJS.

You’ll learn how to:

  • build reusable, composable UI components on the backend,
  • manage component variants with Class Variance Authority (CVA) in server-rendered templates,
  • integrate Tailwind CSS v4 with NestJS and Fastify,
  • build an interactive, server-rendered authentication flow and dashboard,
  • and make informed decisions about when to use server-side rendering versus client-side rendering. 

Is This Guide Right for You?

This guide is designed for developers who:

  • Love the React component model, but need SSR for specific use cases
  • Work with NestJS and want to improve their template-based UI development
  • Need server-rendered authentication or admin interfaces
  • Want to avoid duplicating UI logic between backend and frontend

 

You should be comfortable with:

  • Basic NestJS concepts (modules, controllers, decorators)
  • TypeScript fundamentals
  • High-level understanding of server-side rendering
  • Basic HTML templating concepts 

Why Server-Side Rendering Still Matters

Before diving into implementation, let’s understand when and why SSR is the right choice. 

Use Cases Where SSR Excels

  1. Authentication Flows
  • Sign-in, sign-up, and password reset pages
  • Session management happens server-side
  • Reduced attack surface (no client-side auth logic)
  • Faster time-to-interactive for critical flows

2. Admin Panels and Dashboards

  • Often behind authentication
  • Don’t need heavy client-side interactivity
  • Benefit from server-side data access
  • Simpler deployment (no separate frontend build)

3. SEO-Critical Pages

  • Landing pages, marketing content
  • Blog posts, documentation
  • E-commerce product pages

4. Progressive Enhancement

  • Core functionality works without JavaScript
  • Enhanced with client-side features when available
  • Better accessibility and resilience 

The Traditional SSR Problem

The challenge with traditional SSR approaches is maintainability. Consider a typical scenario: 

 

Every button requires manual entry of class names. When your design system changes, you need to find and update every instance. There’s no reusability, and no single source of truth. 

Our Solution: Backend Components

With our approach, the same buttons become:

This gives you:

  • Single source of truth for component styles
  • Variant management using CVA (just like shadcn/ui)
  • Easy refactoring when design changes
  • Familiar API if you’ve used React 

Technology Stack

Our implementation uses a carefully selected stack that balances performance, developer experience, and maintainability. 

 

Core Technologies

NestJS_ASSIST_Software_1

Styling Utilities

NestJS_ASSIST_Software_2

Integration Packages

NestJS_ASSIST_Software_3

[!TIP] Why Dev Dependencies?: The styling utilities (class-variance-authority, clsx, tailwind-merge) are installed as dev dependencies because they’re only needed at build time to generate CSS and component classes. They don’t need to ship the runtime bundle. 

Architecture Overview

Understanding the high-level architecture will help you see how all the pieces fit together. 

NestJS_ASSIST_Software_4

Request Flow

  1. Client Request: Browser requests a page (e.g., /auth/sign-in)
  2. Controller: NestJS controller handles the request, prepares data
  3. View Engine: Fastify’s view engine processes the Handlebars template
  4. Component Rendering: Template uses registered helpers (components)
  5. HTML Generation: Components return HTML strings with computed classes
  6. Response: Complete HTML page sent to client
  7. Asset Loading: Browser loads compiled CSS from static assets 

Key Design Decisions

1. Components as Functions Returning Strings

Unlike React components that return JSX, our components are TypeScript functions that return HTML strings: 

This keeps them: - Framework-agnostic - Easy to test - Simple to port to React later

 

2. Handlebars Helpers as Component Interface

We register components as Handlebars helpers, providing a familiar API:

 

3. CVA for Variant Management

Using the same pattern as shadcn/ui and other modern component libraries:

 

Step-by-Step Implementation

Let's build this system from the ground up.

 

Step 1: Create the NestJS Project

Start by creating a fresh NestJS project using the official CLI:

When prompted, choose your preferred package manager (npm, yarn, or pnpm). For this guide, we’ll use npm.

[!TIP] If you already have a NestJS project, you can skip this step and integrate the following changes into your existing codebase.

 

Step 2: Install Dependencies

Install the required packages for view rendering and styling:

 

What each package does:

  • @nestjs/platform-fastify – Fastify adapter for NestJS (replaces Express)
  • @fastify/view – Template engine integration for Fastify
  • @fastify/static – Serves static files (CSS, images, JavaScript)
  • handlebars – Minimal, logic-less templating language
  • tailwindcss – Core Tailwind CSS framework
  • @tailwindcss/cli – Standalone CLI for compiling Tailwind
  • class-variance-authority – Type-safe component variant management
  • clsx – Utility for constructing className strings conditionally
  • tailwind-merge – Intelligently merges Tailwind classes without conflicts

 

Step 3: Configure Fastify with Handlebars

Replace the default Express adapter with Fastify and configure the view engine.

Update src/main.ts:

[!NOTE] If you don’t have a layout.hbs file hasn't been created yet; you may want to temporarily comment out the layout option to avoid errors. We’ll create it in Step 10.

Configure Asset Copying

NestJS doesn’t copy arbitrary folders into the dist directory by default. Update nest-cli.json to include public, views, and secret-key file (more on this later):

This ensures that: - public/ → dist/public (CSS, images, etc.) - views/ → dist/views (Handlebars templates)

 

Step 4: Configure Tailwind CSS v4

Tailwind v4 dramatically simplifies the setup process with its new CLI-based approach.

Create the global CSS file

Create public/globals.css:

 

Usage:

  • npm run build:css – One-time build for production
  • npm run watch:css – Watch mode for development (auto-recompiles on changes)

[!TIP] Development Workflow: Run npm run watch:css in a separate terminal window while developing. This automatically rebuilds your CSS whenever you change templates or add new Tailwind classes.

 

Step 5: Create the UI Module Structure

Following NestJS best practices, create a dedicated UI module to encapsulate all component-related code.

Your structure should look like this:

 

Step 6: Create Utility Functions

Create src/ui/utils/utils.ts:

 

Why these utilities matter:

  • cn() – Safely merges Tailwind classes, resolving conflicts (e.g., px-4 px-6 becomes px-6)
  • createAttributesStringified() – Converts props to HTML attributes, handling edge cases like boolean attributes

 

Step 7: Create the Button Component

Create src/ui/components/button.ts:

 

Key concepts:

  1. CVA for variants – Define all button styles in one place with type safety
  2. Props interface – TypeScript ensures correct usage
  3. String output – Component returns HTML string (not JSX)
  4. Composability – Can be extended with custom classes via className prop

[!TIP] You can create additional components following this same pattern: input, card, badge, avatar, kpi, etc. Check the GitHub repository for complete examples.

 

Step 8: Register Handlebars Helpers

Create src/ui/helpers/handlebars-helpers.ts:

 

Important concepts:

  • Handlebars.SafeString – Prevents HTML escaping (required for rendering HTML)
  • Block support (options.fn) – Allows nested content like {{#button}}...{{/button}}
  • Conditional helpers – Enable logic in templates without JavaScript

 

Step 9: Create the UI Module

Create src/ui/ui.module.ts:

 

Step 10: Create View Templates

Create the layout template at views/layout.hbs:

 

Step 11: Create Controllers

Create the auth module with the auth controller src/auth/controllers/auth.controller.ts:

 

Running the Application

Now let's see it in action!

1. Build the CSS:

npm run build:css

2. Start the development server:

npm run start:dev

3. (Optional) Watch CSS in a separate terminal:

npm run watch:css

4. Visit the sign-in page:

Open your browser to http://localhost:3000/auth/sign-in

You should see a beautifully styled sign-in page with: - Two demo credential buttons - Email and password inputs - A submit button - All styled with Tailwind CSS - All using your reusable components 

Advanced Patterns

Nested Components

Nested Components

One of the most powerful features is component composition. You can nest components just like in React:

 

Conditional Rendering

Use the registered conditional helpers for dynamic content:

 

Custom Helpers for Complex Logic

For more complex scenarios, create custom helpers:

 

Usage in templates:

 

Comparison with React

Let's compare our backend component approach with React to understand the similarities and differences. 

Similarities

NestJS_ASSIST_Software_6

Differences

NestJS_ASSIST_Software_7

When to Use Each

Use React (or similar) when: - Building highly interactive UIs (dashboards, editors, etc.) - Need client-side state management - Require real-time updates - Building single-page applications (SPAs)

Use our SSR approach when: - Building authentication flows - Creating admin panels with simple interactions - Need fast initial page loads - SEO is critical - Want to minimize JavaScript bundle size - Progressive enhancement is a priority

Migration Path

One of the benefits of this approach is that it’s easy to migrate to React later:

Shared Design System: Your CVA variants can be copied directly to React components

Same API: Props structure is nearly identical

Gradual Migration: Start with SSR, add React for interactive parts

Hybrid Approach: Use SSR for auth, React for dashboard

Example migration:

The variant definitions and utility functions remain identical!

 

Performance Considerations

Server-Side Rendering Benefits

  1. Faster First Contentful Paint (FCP)
  • HTML is rendered on the server
  • No JavaScript parsing required for initial render
  • Especially beneficial on slower devices

2. Reduced Bundle Size

  • No React runtime
  • No hydration code
  • Only CSS and minimal JavaScript for interactions

3. Better SEO

  • Content is immediately available to crawlers
  • No JavaScript execution required
  • Faster indexing

4. Highly cacheable by browsers and CDNs

  • Static HTML responses can be cached effectively
  • CDNs can serve cached content closer to users
  • Reduces server load and improves response times
  • Works well with HTTP caching headers (Cache-Control, ETag)

[!WARNING] Cache Headers: Incorrectly configured cache headers can be dangerous. Avoid caching sensitive pages (e.g., user-specific content) or use appropriate cache-control directives. Public caching of private data can create security vulnerabilities, allowing one user’s data to be served to another.

 

Optimization Tips

1. CSS Optimization

 

2. Caching

Enable caching for static assets:

 

3. Template Caching

Handlebars automatically caches compiled templates in production. Ensure you’re running in production mode:

 

4. Compression

Add compression for responses:

 

Security Best Practices

When building authentication flows with SSR, security is paramount.

1. CSRF Protection

Install CSRF protection:

npm install @fastify/csrf-protection

Configure it:

Use in forms:

 

2. Session Management

Use secure session handling:

npm install @fastify/secure-session

Create a secret-key file using:

 

3. Input Validation

Use class-validator for DTO validation:

npm install class-validator class-transformer

 

4. Rate Limiting

Protect against brute force attacks:

npm install @fastify/rate-limit

 

5. Helmet for Security Headers

npm install @fastify/helmet

 

Troubleshooting

Common Issues and Solutions

1. Tailwind Styles Not Loading

Symptoms: Page renders but has no styling

 

2. Handlebars Helpers Not Working

Symptoms: {{button}} renders as text or throws error

 

3. Components Rendering as Escaped HTML

Symptoms: You see <button class="..."> as text in the browser

 

4. Layout Not Found Error

Symptoms: Error: ENOENT: no such file or directory, open '.../views/layout.hbs'

 

5. Fastify Plugin Registration Errors

Symptoms: Errors about plugin registration order

 

6. TypeScript Compilation Errors

Symptoms: Build fails with type errors

 

Conclusion

By completing this guide, you have successfully architected a robust server-side rendering system that bridges the gap between traditional backend templating and modern component-based design. You have moved beyond simple HTML generation to create a structured, type-safe environment where UI components are first-class citizens.

Instead of relying on heavy client-side frameworks for every interaction, you have implemented a lightweight, performance-first solution that delivers content to users instantly. You have established a scalable pattern using NestJS and Handlebars that mimics the best parts of the React ecosystem composability, prop interfaces, and variant management without the associated runtime cost. This architecture provides a solid foundation for building content-heavy applications where SEO, initial load performance, and maintainability are paramount. 

Next Steps

1. Expand Your Component Library - Create more primitives: card, badge, avatar, table - Build composite components: navbar, sidebar, modal - Add form components: select, checkbox, radio

2. Add Real Authentication - Integrate Passport.js or similar - Implement JWT or session-based auth - Add OAuth providers (Google, GitHub, etc.)

3. Build a Dashboard - Create data visualization components - Add role-based access control 

Further Reading

Official Documentation: - NestJS Documentation – Comprehensive guide to NestJS - Fastify Documentation – High-performance web framework - Handlebars Guide – Templating language reference - Tailwind CSS – Utility-first CSS framework - CVA Documentation – Class Variance Authority

Example Repository: - ReactSSR GitHub Repository – Complete source code with examples 

Acknowledgement

This approach was inspired by: - shadcn/ui – For the CVA pattern and component API design - React – For the component model and composition patterns - NestJS community – For excellent documentation and examples - Tailwind CSS – For making utility-first CSS practical

 

Thank you for reading! If you found this helpful, consider: - Starring the GitHub repository - Sharing your experience and improvements - Reporting issues or suggesting enhancements

Happy coding! 

Share on:

I have read and understood the ASSIST Software website's Terms of Use and Privacy Policy.

Want to stay on top of everything?

Get updates on industry developments and the software solutions we can now create for a smooth digital transformation.

Frequently Asked Questions

1. Can you integrate AI into an existing software product?

Absolutely. Our team can assess your current system and recommend how artificial intelligence features, such as automation, recommendation engines, or predictive analytics, can be integrated effectively. Whether it's enhancing user experience or streamlining operations, we ensure AI is added where it delivers real value without disrupting your core functionality.

2. What types of AI projects has ASSIST Software delivered?

We’ve developed AI solutions across industries, from natural language processing in customer support platforms to computer vision in manufacturing and agriculture. Our expertise spans recommendation systems, intelligent automation, predictive analytics, and custom machine learning models tailored to specific business needs.

3. What is ASSIST Software's development process?  

The Software Development Life Cycle (SDLC) we employ defines the stages for a software project. Our SDLC phases include planning, requirement gathering, product design, development, testing, deployment, and maintenance.

4. What software development methodology does ASSIST Software use?  

ASSIST Software primarily leverages Agile principles for flexibility and adaptability. This means we break down projects into smaller, manageable sprints, allowing continuous feedback and iteration throughout the development cycle. We also incorporate elements from other methodologies to increase efficiency as needed. For example, we use Scrum for project roles and collaboration, and Kanban boards to see workflow and manage tasks. As per the Waterfall approach, we emphasize precise planning and documentation during the initial stages.

5. I'm considering a custom application. Should I focus on a desktop, mobile or web app?  

We can offer software consultancy services to determine the type of software you need based on your specific requirements. Please explore what type of app development would suit your custom build product.   

  • A web application runs on a web browser and is accessible from any device with an internet connection. (e.g., online store, social media platform)   
  • Mobile app developers design applications mainly for smartphones and tablets, such as games and productivity tools. However, they can be extended to other devices, such as smartwatches.    
  • Desktop applications are installed directly on a computer (e.g., photo editing software, word processors).   
  • Enterprise software manages complex business functions within an organization (e.g., Customer Relationship Management (CRM), Enterprise Resource Planning (ERP)).

6. My software product is complex. Are you familiar with the Scaled Agile methodology?

We have been in the software engineering industry for 30 years. During this time, we have worked on bespoke software that needed creative thinking, innovation, and customized solutions. 

Scaled Agile refers to frameworks and practices that help large organizations adopt Agile methodologies. Traditional Agile is designed for small, self-organizing teams. Scaled Agile addresses the challenges of implementing Agile across multiple teams working on complex projects.  

SAFe provides a structured approach for aligning teams, coordinating work, and delivering value at scale. It focuses on collaboration, communication, and continuous delivery for optimal custom software development services. 

7. How do I choose the best collaboration model with ASSIST Software?  

We offer flexible models. Think about your project and see which model would be right for you.   

  • Dedicated Team: Ideal for complex, long-term projects requiring high continuity and collaboration.   
  • Team Augmentation: Perfect for short-term projects or existing teams needing additional expertise.   
  • Project-Based Model: Best for well-defined projects with clear deliverables and a fixed budget.   

Contact us to discuss the advantages and disadvantages of each model. 

ASSIST Software Team Members