π Setting Up Nodeblocks with VITE from Scratch
Build modern React 18 applications with Nodeblocks components using VITE for lightning-fast development β‘
This comprehensive guide will walk you through creating a complete application from scratch using VITE, React 18, and Nodeblocks frontend components. We'll downgrade from React 19 (VITE's current default) to React 18 since Nodeblocks currently does not support React 19. We'll leverage React 18's features including the createRoot API for optimal performance.
π Prerequisitesβ
- Node.js version 18.0 or higher
- npm or yarn package manager
- Basic knowledge of React 18 and TypeScript
- VITE latest version (automatically included)
π― Project Setupβ
Step 1: Create VITE Projectβ
# Create new VITE project with React and TypeScript
npm create vite@latest my-nodeblocks-app -- --template react-ts
# Navigate to project directory
cd my-nodeblocks-app
# Downgrade to React 18 (Nodeblocks compatibility requirement)
npm install react@^18.0.0 react-dom@^18.0.0
# Update TypeScript types to React 18
npm install --save-dev @types/react@^18.0.0 @types/react-dom@^18.0.0
# Install other dependencies
npm install
# Verify React 18 is installed
npm list react react-dom
β οΈ React 19 Support: Nodeblocks currently does not support React 19. We're working on React 19 compatibility and will update our packages soon. For now, please use React 18 as shown above.
Step 2: Configure NPM Registry Accessβ
Before installing Nodeblocks packages, you need to configure access to the private npm registry. Create a .npmrc
file in your project root:
# Create .npmrc file in project root
touch .npmrc
Add the following configuration to your .npmrc
file:
@nodeblocks:registry=https://registry.npmjs.org
@basaldev:registry=https://registry.npmjs.org
//registry.npmjs.org/:_authToken=${NODEBLOCKS_DEV_TOKEN}
Important: You'll need to set the NODEBLOCKS_DEV_TOKEN
environment variable with your access token. Contact the Nodeblocks team to obtain your development token.
# Set your Nodeblocks development token
export NODEBLOCKS_DEV_TOKEN=your_token_here
Step 3: Install Nodeblocks Dependenciesβ
Install the essential Nodeblocks packages:
# Core domain package for state management
npm install @nodeblocks/matching-app-domain
# Essential UI blocks
npm install @nodeblocks/frontend-navbar-block
npm install @nodeblocks/frontend-footer-block
npm install @nodeblocks/frontend-hero-block
npm install @nodeblocks/frontend-signin-block
npm install @nodeblocks/frontend-signup-block
# Product management blocks
npm install @nodeblocks/frontend-create-product-block
npm install @nodeblocks/frontend-list-products-grid-block
# Organization management blocks
npm install @nodeblocks/frontend-create-organization-block
# User management blocks
npm install @nodeblocks/frontend-basic-information-block
npm install @nodeblocks/frontend-list-users-block
ποΈ Basic Application Structureβ
Step 4: Create Main Application Componentβ
Replace the contents of src/App.tsx
:
import React from 'react'
import { Navbar } from '@nodeblocks/frontend-navbar-block'
import { Footer } from '@nodeblocks/frontend-footer-block'
import { MatchingAppDomain } from '@nodeblocks/matching-app-domain'
import { HomePage } from './pages/HomePage'
import { LoginPage } from './pages/LoginPage'
import { SignUpPage } from './pages/SignUpPage'
import { ProductsPage } from './pages/ProductsPage'
import './App.css'
function App() {
return (
<MatchingAppDomain
applicationType={['demand']}
serviceEndpoints={{
user: import.meta.env.VITE_USER_SERVICE_URL || 'https://user-service.example.com',
auth: import.meta.env.VITE_AUTH_SERVICE_URL || 'https://auth-service.example.com',
organization: import.meta.env.VITE_ORG_SERVICE_URL || 'https://org-service.example.com',
catalog: import.meta.env.VITE_CATALOG_SERVICE_URL || 'https://catalog-service.example.com',
chat: import.meta.env.VITE_CHAT_SERVICE_URL || 'https://chat-service.example.com',
order: import.meta.env.VITE_ORDER_SERVICE_URL || 'https://order-service.example.com',
}}
logoutRedirectUrl={'/login'}
loggedInRedirectUrl={'/products'}
sessionType="local_storage">
{({state, middleware, updateState}) => {
return (
<div className="app">
{/* Navigation Bar */}
<Navbar
leftContent={
<div className="navbar-logo">
<img
src="/vite.svg"
alt="Company Logo"
style={{height: '32px'}}
/>
<span className="brand-name">MyApp</span>
</div>
}
centerContent={
<nav className="main-navigation">
<a href="/" className="nav-link">Home</a>
<a href="/positions" className="nav-link">Products</a>
</nav>
}
rightContent={
<div className="auth-buttons">
{state.loggedInUser ? (
<button
className="btn-logout"
onClick={() => middleware.onActionLogout()}
>
Logout
</button>
) : (
<>
<a href="/login" className="btn-login">Login</a>
<a href="/sign-up" className="btn-signup">Sign Up</a>
</>
)}
</div>
}
/>
{/* Main Content */}
<main className="main-content">
{renderPage(state.currentPage, state, middleware, updateState)}
</main>
{/* Footer */}
<Footer
logoSrc="/vite.svg"
content={
<div className="footer-content">
<div className="footer-section">
<h4>Company</h4>
<ul>
<li><a href="/about">About Us</a></li>
<li><a href="/careers">Careers</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</div>
<div className="footer-section">
<h4>Support</h4>
<ul>
<li><a href="/help">Help Center</a></li>
<li><a href="/docs">Documentation</a></li>
<li><a href="/api">API</a></li>
</ul>
</div>
</div>
}
copyright="Β© 2024 Your Company Name. All rights reserved."
/>
</div>
)
}}
</MatchingAppDomain>
)
}
// Page routing function
function renderPage(currentPage: string, state: any, middleware: any, updateState: any) {
switch (currentPage) {
case 'home':
return <HomePage />
case 'login':
return <LoginPage middleware={middleware} />
case 'sign_up':
return <SignUpPage middleware={middleware} />
case 'positions_list':
return <ProductsPage />
default:
return <div>Not Found</div>
}
}
export default App
π± Creating Page Componentsβ
Step 5: Home Page with Hero Blockβ
Create src/pages/HomePage.tsx
:
import React from 'react'
import { Hero } from '@nodeblocks/frontend-hero-block'
export function HomePage() {
return (
<div className="home-page">
<Hero
title="Welcome to Nodeblocks"
secondaryText="Build modern applications faster with our comprehensive component library"
imageUrl='https://images.unsplash.com/photo-1557804506-669a67965ba0?auto=format&fit=crop&w=1920&q=80'
buttonText="Get Started"
byline="Build modern applications faster with our comprehensive component library"
onClickButton={() => {
console.log("Get Started")
}}
/>
<section className="features-section">
<div className="container">
<h2>Why Choose Nodeblocks?</h2>
<div className="features-grid">
<div className="feature-card">
<h3>π Fast Development</h3>
<p>Reduce development time by 70% with pre-built components</p>
</div>
<div className="feature-card">
<h3>π¨ Customizable</h3>
<p>Fully customizable components that adapt to your brand</p>
</div>
<div className="feature-card">
<h3>β‘ Performance</h3>
<p>Optimized for speed and built with modern React practices</p>
</div>
</div>
</div>
</section>
</div>
)
}c
Step 6: Authentication Pagesβ
Create src/pages/LoginPage.tsx
:
import React from 'react'
import { SignIn } from '@nodeblocks/frontend-signin-block'
interface LoginPageProps {
middleware: {
onActionLogin: (email: string, password: string) => Promise<void>
}
}
export function LoginPage({ middleware }: LoginPageProps) {
return (
<div className="auth-page">
<div className="auth-container">
<SignIn
onChange={(setError, getValues, clearErrors) => {
const values = getValues()
console.log('Form values:', values)
// Custom validation
if (!values.email) {
setError('email', {message: 'Email is required', type: 'required'})
} else if (!/\S+@\S+\.\S+/.test(values.email)) {
setError('email', {message: 'Email is invalid', type: 'pattern'})
} else {
clearErrors('email')
}
}}
onSubmit={async (formData) => {
try {
await middleware.onActionLogin(formData.email || '', formData.password || '')
console.log('Login successful')
} catch (error) {
console.error('Login failed:', error)
}
}}
style={{
maxWidth: '400px',
margin: '2rem auto',
padding: '2rem',
borderRadius: '12px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
backgroundColor: 'white'
}}>
<SignIn.SignInTitle>Welcome Back</SignIn.SignInTitle>
<SignIn.EmailField
label="Email Address"
placeholder="Enter your email"
/>
<SignIn.PasswordField
label="Password"
placeholder="Enter your password"
/>
<SignIn.SignInButton>Sign In</SignIn.SignInButton>
<SignIn.GotoSignUp>
<a href="/signup">Don't have an account? Sign up</a>
</SignIn.GotoSignUp>
<SignIn.ResetPassword>
<a href="/reset-password">Forgot your password?</a>
</SignIn.ResetPassword>
</SignIn>
</div>
</div>
)
}
Create src/pages/SignUpPage.tsx
:
import React from 'react'
import { SignUp } from '@nodeblocks/frontend-signup-block'
interface SignUpPageProps {
middleware: {
onActionRegister: (formData: {
email?: string;
password?: string;
agreesPrivacyPolicy?: boolean;
agreesUserAgreement?: boolean;
}) => Promise<void>
}
}
export function SignUpPage({ middleware }: SignUpPageProps) {
return (
<div className="auth-page">
<div className="auth-container">
<SignUp
onChange={(setError, getValues, clearErrors) => {
const values = getValues()
// Custom validation logic
if (values.password && values.confirmPassword &&
values.password !== values.confirmPassword) {
setError('confirmPassword', {
message: 'Passwords do not match',
type: 'validate'
})
} else {
clearErrors('confirmPassword')
}
}}
onSubmit={async (formData) => {
try {
await middleware.onActionRegister(formData)
console.log('Registration successful')
} catch (error) {
console.error('Registration failed:', error)
}
}}
style={{
maxWidth: '400px',
margin: '2rem auto',
padding: '2rem',
borderRadius: '12px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
backgroundColor: 'white'
}}>
<SignUp.SignUpTitle>Create Your Account</SignUp.SignUpTitle>
<SignUp.EmailField name="email" label="Email Address" placeholder="Enter your email" />
<SignUp.PasswordField name="password" label="Password" placeholder="Create password" />
<SignUp.PasswordField name="confirmPassword" label="Confirm Password" placeholder="Confirm password" inputType="password" />
<SignUp.TermsOfUse name="agreesUserAgreement" label="I agree to the terms of use" />
<SignUp.PrivacyPolicy name="agreesPrivacyPolicy" label="I agree to the privacy policy" />
<SignUp.SignUpButton>Create Account</SignUp.SignUpButton>
<SignUp.GotoSignIn>
<a href="/login">Already have an account? Sign in</a>
</SignUp.GotoSignIn>
</SignUp>
</div>
</div>
)
}
Step 7: Products Pageβ
Create src/pages/ProductsPage.tsx
:
import React, { useState } from 'react'
import { CreateProduct } from '@nodeblocks/frontend-create-product-block'
import { ListProductsGrid } from '@nodeblocks/frontend-list-products-grid-block'
export function ProductsPage() {
const [showCreateForm, setShowCreateForm] = useState(false)
const [products, setProducts] = useState([
{
id: '1',
name: 'Modern Laptop',
price: 1299.99,
category: 'electronics',
imageUrl: 'https://images.unsplash.com/photo-1496181133206-80ce9b88a853?w=300',
description: 'High-performance laptop for professionals'
},
{
id: '2',
name: 'Wireless Headphones',
price: 199.99,
category: 'electronics',
imageUrl: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=300',
description: 'Premium wireless headphones with noise cancellation'
}
])
const categoryOptions = [
{ value: 'electronics', label: 'Electronics' },
{ value: 'clothing', label: 'Clothing' },
{ value: 'home', label: 'Home & Garden' },
{ value: 'books', label: 'Books' },
{ value: 'sports', label: 'Sports & Recreation' }
]
return (
<div className="products-page">
<div className="container">
<div className="page-header">
<h1>Product Management</h1>
<button
className="btn-primary"
onClick={() => setShowCreateForm(!showCreateForm)}
>
{showCreateForm ? 'View Products' : 'Add Product'}
</button>
</div>
{showCreateForm ? (
<div className="create-product-section">
<h2>Create New Product</h2>
<CreateProduct
categoryOptions={categoryOptions}
onSubmit={(formData) => {
console.log('Creating product:', formData)
// Add product to list
const newProduct = {
id: Date.now().toString(),
name: formData.name || '',
price: Number(formData.price) || 0,
category: String(formData.category) || '',
imageUrl: formData.image?.url || 'https://via.placeholder.com/300',
description: formData.description || ''
}
setProducts([...products, newProduct])
setShowCreateForm(false)
}}
onChange={(setError, getValues, clearErrors) => {
const values = getValues()
console.log('Form values:', values)
}}
onAcceptAttachment={(files) => {
console.log('Files accepted:', files)
}}
onUploadAttachment={(file) => {
console.log('Uploading file:', file)
// Handle file upload logic here
return Promise.resolve({
url: 'https://via.placeholder.com/300',
name: file.name
})
}}
onClearAttachment={() => {
console.log('Attachment cleared')
}}
/>
</div>
) : (
<div className="products-list-section">
<ListProductsGrid listProductsGridTitle="Featured Products" subtitle="Latest Collection">
<ListProductsGrid.Items>
{products.map(product => (
<ListProductsGrid.Items.GridCard key={product.id} title={product.name} subtitle={product.description} imageUrl={product.imageUrl} />
))}
</ListProductsGrid.Items>
</ListProductsGrid>
</div>
)}
</div>
</div>
)
}
π¨ Styling Your Applicationβ
Step 8: Add Custom Stylesβ
Update src/App.css
:
/* Reset and base styles */
root {
width: 100%;
max-width: 100%;
margin: 0 auto;
padding: 0;
text-align: center;
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}
/* Reset and base styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
line-height: 1.6;
color: #333;
background-color: #f8fafc;
}
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.main-content {
flex: 1;
}
/* Container */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
/* Navigation styles */
.navbar-logo {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: bold;
color: #1a202c;
}
.brand-name {
font-size: 1.5rem;
}
.main-navigation {
display: flex;
gap: 2rem;
}
.nav-link {
text-decoration: none;
color: #4a5568;
font-weight: 500;
transition: color 0.2s;
}
.nav-link:hover {
color: #2d3748;
}
.auth-buttons {
display: flex;
gap: 1rem;
align-items: center;
}
.btn-login, .btn-signup, .btn-logout {
padding: 0.5rem 1rem;
border-radius: 6px;
text-decoration: none;
font-weight: 500;
transition: all 0.2s;
border: none;
cursor: pointer;
}
.btn-login {
color: #4a5568;
background: transparent;
}
.btn-login:hover {
background-color: #f7fafc;
}
.btn-signup, .btn-logout {
background-color: #3182ce;
color: white;
}
.btn-signup:hover, .btn-logout:hover {
background-color: #fff;
color: #3182ce;
}
/* Page styles */
.home-page {
min-height: calc(100vh - 160px);
}
.features-section {
padding: 4rem 0;
background: white;
}
.features-section h2 {
text-align: center;
margin-bottom: 3rem;
font-size: 2.5rem;
color: #2d3748;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.feature-card {
padding: 2rem;
border-radius: 12px;
background: #f7fafc;
border: 1px solid #e2e8f0;
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
}
.feature-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.feature-card h3 {
margin-bottom: 1rem;
color: #2d3748;
}
/* Auth pages */
.auth-page {
min-height: calc(100vh - 160px);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.auth-container {
width: 100%;
max-width: 500px;
}
/* Products page */
.products-page {
padding: 2rem 0;
}
.page-header {
display: flex;
justify-content: between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e2e8f0;
}
.page-header h1 {
color: #2d3748;
font-size: 2.5rem;
}
.btn-primary {
background-color: #3182ce;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
}
.btn-primary:hover {
background-color: #2c5282;
}
input {
background-color: #fff;
}
.create-product-section, .products-list-section {
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
/* Footer styles */
.footer-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.footer-section h4 {
margin-bottom: 1rem;
color: white;
}
.footer-section ul {
list-style: none;
}
.footer-section li {
margin-bottom: 0.5rem;
}
.footer-section a {
color: #4a5568;
text-decoration: none;
transition: color 0.2s;
}
.footer-section a:hover {
color: #2d3748;
}
/* Responsive design */
@media (max-width: 768px) {
.main-navigation {
display: none;
}
.features-grid {
grid-template-columns: 1fr;
}
.page-header {
flex-direction: column;
gap: 1rem;
align-items: stretch;
}
.auth-buttons {
flex-direction: column;
}
}
π§ Environment Configurationβ
Step 9: Environment Variablesβ
Create .env
file in your project root:
# API Endpoints
VITE_USER_SERVICE_URL=https://user-service.example.com
VITE_AUTH_SERVICE_URL=https://auth-service.example.com
VITE_ORG_SERVICE_URL=https://organization-service.example.com
VITE_CATALOG_SERVICE_URL=https://catalog-service.example.com
VITE_CHAT_SERVICE_URL=https://chat-service.example.com
VITE_ORDER_SERVICE_URL=https://order-service.example.com
# Application Configuration
VITE_APP_NAME=MyNodeBlocksApp
VITE_APP_VERSION=1.0.0
Create .env.development
for development:
# Development API Endpoints
VITE_USER_SERVICE_URL=http://localhost:8001
VITE_AUTH_SERVICE_URL=http://localhost:8002
VITE_ORG_SERVICE_URL=http://localhost:8003
VITE_CATALOG_SERVICE_URL=http://localhost:8004
VITE_CHAT_SERVICE_URL=http://localhost:8005
VITE_ORDER_SERVICE_URL=http://localhost:8006
π Running Your Applicationβ
Step 10: Start Development Serverβ
# Start the development server
npm run dev
# Your application will be available at:
# http://localhost:3000
Step 11: Build for Productionβ
# Build for production
npm run build
# Preview production build
npm run preview
π Advanced Featuresβ
Adding More Blocksβ
As your application grows, you can add more specialized blocks:
# Organization management
npm install @nodeblocks/frontend-list-organizations-block
# Order management
npm install @nodeblocks/frontend-create-order-block
npm install @nodeblocks/frontend-list-orders-block
# Chat functionality
npm install @nodeblocks/frontend-chat-conversation-block
npm install @nodeblocks/frontend-chat-conversation-list-block
# Advanced UI components
npm install @nodeblocks/frontend-filter-search-panel-block
npm install @nodeblocks/frontend-breadcrumb-block
npm install @nodeblocks/frontend-side-navigation-block
Custom Block Overridingβ
All Nodeblocks components support the powerful block overriding pattern:
<CreateProduct categoryOptions={categoryOptions}>
{({defaultBlocks, defaultBlockOrder}) => ({
blocks: {
...defaultBlocks,
// Override specific blocks
productName: {
...defaultBlocks.productName,
props: {
...defaultBlocks.productName.props,
placeholder: "Enter your amazing product name",
style: { fontSize: '1.2rem' }
}
}
},
blockOrder: defaultBlockOrder
})}
</CreateProduct>
Learn More: For comprehensive examples and advanced block overriding techniques, see our Advanced Usage Guide.
π Useful Resourcesβ
Built with β€οΈ using Nodeblocks and VITE for maximum development speed and performance