Installation & Setup
Get started with Harpy.js in minutes. Choose between using the CLI to scaffold a new full-stack project or integrating Harpy.js into an existing NestJS application for server-side React rendering.
Quick tip: The CLI is the fastest path to a production-ready full-stack application. Use manual integration only if you're adding Harpy.js to an existing NestJS project.
System Requirements
Minimum
- Node.js: 18+ (20.x recommended for latest features) — native ESM & modern runtime APIs
- Package manager: pnpm (recommended), npm, or yarn
- Git installed
Installation Methods
Choose the method that fits your workflow. The CLI scaffolds a complete project; manual installation is for integrating into existing projects.
Quick Start (recommended)
Use the Harpy CLI to create a new project with sensible defaults and a working dev server.
pnpm i -g @harpy-js/cliharpy create my-appcd my-apppnpm dev
The CLI will scaffold routes, layouts, a working dev server and example pages so you can focus on building features.
Manual Installation
Install the core packages and configure the rendering engine if you prefer to add Harpy.js to an existing project.
pnpm add @harpy-js/core react react-dom
You'll then set up a layout, configure rendering in your server entry, and add decorators to your controllers (if using NestJS or similar) to render JSX pages.
Why JSX/layouts? Harpy.js uses JSX-based layouts to enable full server-side rendering plus client hydration — layouts are components that wrap pages. Ensure your environment supports JSX tooling.
Detailed Steps
1. Install Core Dependencies
Add the Harpy.js core runtime, React, NestJS, and required adapters to your project. The core package includes the JSX rendering engine, automatic hydration, and utilities for server-side React rendering.
pnpm add @harpy-js/core @nestjs/common @nestjs/core @nestjs/platform-fastify react react-dom reflect-metadata
@harpy-js/core— JSX engine, hydration, routing, and decorators@nestjs/platform-fastify— Required adapter (Fastify only)react react-dom— UI framework (React 18+)
2. Create a Root Layout Component
Layouts wrap your pages and receive metadata (SEO tags, Open Graph, Twitter), hydration scripts for client components, and optional navigation sections. This is the HTML shell that renders on the server.
1// src/layouts/layout.tsx2import React from 'react';3import { type JsxLayoutProps } from '@harpy-js/core';45export default function MainLayout({ 6 children, 7 meta, 8 hydrationScripts 9}: JsxLayoutProps) {10 const title = meta?.title ?? 'My App';11 const description = meta?.description ?? '';12 const canonical = meta?.canonical;13 const keywords = meta?.keywords;14 const og = meta?.openGraph ?? {};15 const twitter = meta?.twitter ?? {};16 17 return (18 <html lang="en">19 <head>20 <meta charSet="utf-8" />21 <meta name="viewport" content="width=device-width, initial-scale=1" />22 <title>{title}</title>23 24 {description && <meta name="description" content={description} />}25 {keywords && <meta name="keywords" content={keywords} />}26 {canonical && <link rel="canonical" href={canonical} />}27 28 {/* Open Graph */}29 {og.title && <meta property="og:title" content={og.title} />}30 {og.description && <meta property="og:description" content={og.description} />}31 {og.type && <meta property="og:type" content={og.type} />}32 {og.image && <meta property="og:image" content={og.image} />}33 {og.url && <meta property="og:url" content={og.url} />}34 35 {/* Twitter Card */}36 {twitter.card && <meta name="twitter:card" content={twitter.card} />}37 {twitter.title && <meta name="twitter:title" content={twitter.title} />}38 {twitter.description && <meta name="twitter:description" content={twitter.description} />}39 {twitter.image && <meta name="twitter:image" content={twitter.image} />}40 41 <link rel="stylesheet" href="/styles/styles.css" />42 </head>43 <body>44 {/* Page content */}45 <main>{children}</main>46 47 {/* Auto-injected client hydration scripts */}48 {hydrationScripts?.map((script) => (49 <script 50 key={script.componentName} 51 src={script.path}52 type="module"53 async54 />55 ))}56 </body>57 </html>58 );59}- The layout receives SEO metadata from your controllers
hydrationScriptscontains auto-generated client component bundles- This HTML is rendered on the server and sent as the initial response
- Client components hydrate automatically in the browser
3. Bootstrap Your NestJS Application
Configure your NestJS server to use Fastify adapter and initialize the Harpy JSX rendering engine. The setupHarpyApp function handles JSX engine initialization, static file serving, and automatic hydration setup.
1// src/main.ts2import 'reflect-metadata';3import { NestFactory } from '@nestjs/core';4import { FastifyAdapter } from '@nestjs/platform-fastify';5import { AppModule } from './app.module';6import { setupHarpyApp } from '@harpy-js/core';7import type { NestFastifyApplication } from '@nestjs/platform-fastify';8import fastifyStatic from '@fastify/static';9import DefaultLayout from './layouts/layout';10import * as path from 'path';1112async function bootstrap() {13 const app = await NestFactory.create<NestFastifyApplication>(14 AppModule,15 new FastifyAdapter(),16 );1718 // Centralized Harpy setup: JSX engine, cookies, and static handlers19 await setupHarpyApp(app, {20 layout: DefaultLayout, // Root layout component, this the layout that wraps all pages unless you override it per-page in the controller21 distDir: 'dist', // In case you changed the output dir in tsconfig.json, consider updating this22 publicDir: 'public', // Directory for static assets like images, fonts, etc.23 });2425 await app.listen({26 port: process.env.PORT || 3000,27 host: '0.0.0.0',28 });2930 console.log(`Application running on: ${await app.getUrl()}`);31}3233bootstrap();- Registers the JSX rendering interceptor on all HTTP responses
- Configures automatic hydration script injection
- Sets up cookie middleware for client-side interactivity
- Initializes the NavigationService for routing management
4. Create Controllers & Render JSX Pages
Use the @JsxRender decorator to render React components as HTML on the server. Your controller returns page data, and the decorator handles rendering and automatic hydration.
1// src/features/home/home.controller.ts2import { Controller, Get } from '@nestjs/common';3import { JsxRender, type PageProps } from '@harpy-js/core';4import HomePage from './views/home-page';56export interface HomePageProps extends PageProps {7 title: string;8 timestamp: string;9}1011@Controller()12export class HomeController {13 @Get()14 @JsxRender(HomePage, {15 meta: {16 title: 'Home - My Full-Stack App',17 description: 'Welcome to my high-performance full-stack application',18 keywords: 'home, welcome, React SSR, NestJS',19 canonical: 'https://myapp.com/',20 openGraph: {21 title: 'Home',22 description: 'Welcome to my app',23 type: 'website',24 url: 'https://myapp.com/',25 image: 'https://myapp.com/og-image.png',26 },27 twitter: {28 card: 'summary_large_image',29 title: 'Home',30 description: 'Welcome to my app',31 },32 },33 })34 async home(): Promise<HomePageProps> {35 return {36 title: 'Welcome to Harpy.js',37 timestamp: new Date().toISOString(),38 };39 }40}1// src/features/home/views/home-page.tsx2import React from 'react';3import { HomePageProps } from '../home.controller';45export default function HomePage({ title, timestamp }: HomePageProps) {6 return (7 <div className="p-8 bg-gradient-to-br from-amber-50 to-orange-50 rounded-lg">8 <h1 className="text-4xl font-bold text-slate-900 mb-2">{title}</h1>9 <p className="text-slate-600">Built at: {timestamp}</p>10 </div>11 );12}- NestJS calls your controller method
@JsxRenderrenders HomePage component on the server- HTML with proper head tags is sent to the browser
- Client components automatically hydrate for interactivity
- Instant first paint with full SEO support
meta field supports title, description, keywords, canonical, openGraph (title, description, type, image, url), and twitter (card, title, description, image).5. Build & Run Your Application
Compile your TypeScript code and start the development server. Harpy.js will automatically detect client components and generate hydration bundles.
1# Build for production2pnpm build34# Run development server with hot reload5pnpm dev67# Run production build8pnpm startYour application will be available at http://localhost:3000
- Hot module reloading for controllers and pages
- Automatic hydration bundle regeneration
- Fast refresh for client components
- TypeScript compilation with source maps
Recommended Project Structure
Harpy.js follows a feature-based modular architecture optimized for server-side rendering, automatic hydration, and scalability. This structure keeps related code (controllers, views, services) together while separating shared utilities.
my-fullstack-app/├─ src/│ ├─ assets/ # Images, fonts, and static files│ │ └─ styles.css # Global Tailwind CSS (required)│ ├─ components/ # Reusable UI components (buttons, cards, etc.)│ ├─ layouts/ # Layout wrappers (default, dashboard, etc.)│ │ └─ layout.tsx # Root layout with HTML shell│ ├─ features/ # Feature modules (one per domain)│ │ ├─ home/│ │ │ ├─ home.controller.ts│ │ │ ├─ home.module.ts│ │ │ └─ views/│ │ │ └─ home-page.tsx│ │ ├─ docs/│ │ │ ├─ docs.controller.ts│ │ │ ├─ docs.module.ts│ │ │ └─ views/│ │ └─ auth/│ │ ├─ auth.controller.ts│ │ └─ auth.service.ts│ ├─ i18n/ # Internationalization│ │ ├─ get-dictionary.ts│ │ └─ translations/│ ├─ main.ts # Server bootstrap (NestJS + Fastify)│ └─ app.module.ts # Root NestJS module├─ public/ # Public static files (images, favicon, etc.)├─ dist/ # Compiled output + hydration bundles├─ package.json├─ tsconfig.json├─ tailwind.config.js # Tailwind CSS configuration└─ README.mdCore Directories
src/main.ts— Bootstrap file that initializes NestJS with Fastify and setupHarpyApp()src/layouts/layout.tsx— Root HTML layout receiving metadata and hydration scriptssrc/features/— Domain-based modules (home, auth, docs) each with controllers, services, viewssrc/components/— Shared UI components (both server and client)dist/— Build output including hydration manifests and client bundles
How It Works
- Controllers use
@JsxRender()to render pages - Pages render on the server with proper HTML head tags
- Client components marked with
'use client'are detected and bundled - Hydration scripts automatically inject into the layout
- Browser loads HTML, hydrates client components, app becomes interactive
Best Practices
- Keep features self-contained with their own controllers, services, and views
- Use layouts for common UI shells (different layouts for public vs. dashboard pages)
- Place only reusable components in
src/components/ - Mark interactive components with
'use client'at the top - Leverage NestJS services for business logic and data fetching