メインコンテンツまでスキップ

🚀 スクラッチセットアップガイド

Viteを使用して超高速開発でモダンなReact 19アプリケーションをNodeblocksコンポーネントで構築

この包括的なガイドでは、Vite、React 19、Nodeblocksフロントエンドコンポーネントを使用してスクラッチから完全なアプリケーションを作成する方法を説明します。


📋 前提条件

  • Node.js バージョン18.0以上
  • npm または yarn パッケージマネージャー
  • React 19TypeScript の基本知識
  • Vite 最新バージョン(自動的に含まれます)

🎯 プロジェクト設定

ステップ1: Viteプロジェクトの作成

# ReactとTypeScriptで新しいViteプロジェクトを作成
npm create vite@latest my-nodeblocks-app -- --template react-ts

# プロジェクトディレクトリに移動
cd my-nodeblocks-app

npm install react@^19.0.0 react-dom@^19.0.0

# TypeScript型をReact 19に更新
npm install --save-dev @types/react@^19.0.0 @types/react-dom@^19.0.0

# 他の依存関係をインストール
npm install

# React 19がインストールされていることを確認
npm list react react-dom

ステップ2: NPMレジストリアクセスの設定

Nodeblocksパッケージをインストールする前に、プライベートnpmレジストリへのアクセスを設定する必要があります。プロジェクトルートに.npmrcファイルを作成します:

# プロジェクトルートに.npmrcファイルを作成
touch .npmrc

.npmrcファイルに以下の設定を追加します:

@nodeblocks:registry=https://registry.npmjs.org
@basaldev:registry=https://registry.npmjs.org
//registry.npmjs.org/:_authToken=${NODEBLOCKS_DEV_TOKEN}

重要: アクセストークンでNODEBLOCKS_DEV_TOKEN環境変数を設定する必要があります。開発トークンを取得するには、Nodeblocksチームにお問い合わせください。

# Nodeblocks開発トークンを設定
export NODEBLOCKS_DEV_TOKEN=your_token_here

ステップ3: Nodeblocks依存関係のインストール

必須のNodeblocksパッケージをインストールします:

# 状態管理のためのコアドメインパッケージ
npm install @nodeblocks/matching-app-domain

# テーマパッケージ(推奨)
npm install @nodeblocks/frontend-theme

# 必須UIブロック
npm install @nodeblocks/frontend-navbar-block@0.2.0
npm install @nodeblocks/frontend-footer-block@0.2.0
npm install @nodeblocks/frontend-hero-block@0.2.0
npm install @nodeblocks/frontend-signin-block@0.3.0
npm install @nodeblocks/frontend-signup-block@0.4.0

# 商品管理ブロック
npm install @nodeblocks/frontend-create-product-block@0.5.0
npm install @nodeblocks/frontend-list-products-grid-block@0.2.0

# 組織管理ブロック
npm install @nodeblocks/frontend-create-organization-block

# ユーザー管理ブロック
npm install @nodeblocks/frontend-basic-information-block
npm install @nodeblocks/frontend-list-users-block@0.2.0

🏗️ 基本的なアプリケーション構造

ステップ4: メインアプリケーションコンポーネントの作成

src/App.tsxの内容を置き換えます:

import React from 'react'
import { ThemeProvider } from '@nodeblocks/frontend-theme'
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 (
<ThemeProvider>
<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">
{/* ナビゲーションバー */}
<Navbar
leftContent={
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<Navbar.Logo src="/vite.svg" alt="会社ロゴ" style={{ height: 32 }} />
<span className="brand-name">マイアプリ</span>
</div>
}
centerContent={
<div style={{ display: 'flex', gap: 16 }}>
<Navbar.Link href="/">ホーム</Navbar.Link>
<Navbar.Link href="/positions">商品</Navbar.Link>
</div>
}
rightContent={
state.loggedInUser ? (
<Navbar.ButtonLink onClick={() => middleware.onActionLogout()}>
ログアウト
</Navbar.ButtonLink>
) : (
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
<Navbar.Link href="/login">ログイン</Navbar.Link>
<Navbar.ButtonLink href="/sign-up">サインアップ</Navbar.ButtonLink>
</div>
)
}
sx={{ backgroundColor: 'white', boxShadow: 1 }}>
<Navbar.Left />
<Navbar.Center />
<Navbar.Right />
</Navbar>

{/* メインコンテンツ */}
<main className="main-content">
{renderPage(state.currentPage, state, middleware, updateState)}
</main>

{/* フッター */}
<Footer
logoSrc="/vite.svg"
content={
<div className="footer-content">
<div className="footer-section">
<h4>会社</h4>
<ul>
<li><a href="/about">私たちについて</a></li>
<li><a href="/careers">採用情報</a></li>
<li><a href="/contact">お問い合わせ</a></li>
</ul>
</div>
<div className="footer-section">
<h4>サポート</h4>
<ul>
<li><a href="/help">ヘルプセンター</a></li>
<li><a href="/docs">ドキュメント</a></li>
<li><a href="/api">API</a></li>
</ul>
</div>
</div>
}
copyright="© 2025 Your Company Name. All rights reserved.">
<Footer.Logo />
<Footer.Content />
<Footer.Copyright />
</Footer>
</div>
)
}}
</MatchingAppDomain>
</ThemeProvider>
)
}

// ページルーティング関数
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>見つかりません</div>
}
}

export default App

💡 注: ThemeProviderがアプリ全体をラップして、一貫したMUIテーマを提供します。カスタマイズオプションについてはテーマガイドをご覧ください。


📱 ページコンポーネントの作成

ステップ5: ヒーローブロック付きホームページ

src/pages/HomePage.tsxを作成:

import React from 'react'
import { Hero } from '@nodeblocks/frontend-hero-block'

export function HomePage() {
return (
<div className="home-page">
<Hero
byline="Nodeblocksへようこそ"
secondaryText="包括的なコンポーネントライブラリでモダンなアプリケーションをより速く構築"
imageUrl="https://images.unsplash.com/photo-1557804506-669a67965ba0?auto=format&fit=crop&w=1920&q=80"
buttonText="始める"
onClickButton={() => console.log("開始")}>
<Hero.HeroContent>
<Hero.HeroContent.HeroByline />
<Hero.HeroContent.SecondaryText />
<Hero.HeroContent.ActionButton />
</Hero.HeroContent>
<Hero.HeroImg />
</Hero>

<section className="features-section">
<div className="container">
<h2>なぜNodeblocksを選ぶのか?</h2>
<div className="features-grid">
<div className="feature-card">
<h3>🚀 高速開発</h3>
<p>事前構築されたコンポーネントで開発時間を70%短縮</p>
</div>
<div className="feature-card">
<h3>🎨 カスタマイズ可能</h3>
<p>ブランドに適応する完全にカスタマイズ可能なコンポーネント</p>
</div>
<div className="feature-card">
<h3>⚡ パフォーマンス</h3>
<p>速度のために最適化され、モダンなReactプラクティスで構築</p>
</div>
</div>
</div>
</section>
</div>
)
}

📚 詳細: 高度なカスタマイズオプションについてはHeroブロックドキュメントをご覧ください。

ステップ6: 認証ページ

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()

// カスタムバリデーション
if (!values.email) {
setError('email', { message: 'メールアドレスは必須です', type: 'required' })
} else if (!/\S+@\S+\.\S+/.test(values.email)) {
setError('email', { message: 'メールアドレスが無効です', type: 'pattern' })
} else {
clearErrors('email')
}
}}
onSubmit={async (formData) => {
try {
await middleware.onActionLogin(formData.email || '', formData.password || '')
console.log('ログイン成功')
} catch (error) {
console.error('ログイン失敗:', error)
}
}}
signupUrl="/sign-up"
resetPasswordUrl="/reset-password"
sx={{ maxWidth: 400, mx: 'auto' }}>
<SignIn.SignInTitle>おかえりなさい</SignIn.SignInTitle>
<SignIn.EmailField label="メールアドレス" placeholder="メールアドレスを入力" />
<SignIn.PasswordField label="パスワード" placeholder="パスワードを入力" />
<SignIn.SignInButton>サインイン</SignIn.SignInButton>
<SignIn.GotoSignUp>
<span>アカウントをお持ちでないですか? <a href="/sign-up">サインアップ</a></span>
</SignIn.GotoSignUp>
<SignIn.ResetPassword>
<a href="/reset-password">パスワードをお忘れですか?</a>
</SignIn.ResetPassword>
</SignIn>
</div>
</div>
)
}

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 { email, password } = getValues()

// カスタムバリデーション
if (email && !email.includes('@')) {
setError('email', { message: '有効なメールアドレスを入力してください' })
} else {
clearErrors('email')
}

if (password && password.length < 8) {
setError('password', { message: 'パスワードは8文字以上必要です' })
} else {
clearErrors('password')
}
}}
onSubmit={async (formData) => {
try {
await middleware.onActionRegister(formData)
console.log('登録成功')
} catch (error) {
console.error('登録失敗:', error)
}
}}
termsOfUseUrl="/terms"
privacyPolicyUrl="/privacy"
loginUrl="/login"
sx={{ maxWidth: 400, mx: 'auto' }}>
<SignUp.SignUpTitle>アカウントを作成</SignUp.SignUpTitle>
<SignUp.EmailField label="メールアドレス" placeholder="メールアドレスを入力" />
<SignUp.PasswordField label="パスワード" placeholder="安全なパスワードを作成" />
<SignUp.TermsOfUse name="agreesUserAgreement" label="利用規約に同意します" />
<SignUp.PrivacyPolicy name="agreesPrivacyPolicy" label="プライバシーポリシーに同意します" />
<SignUp.SignUpButton>アカウント作成</SignUp.SignUpButton>
<SignUp.GotoSignIn>
<span>既にアカウントをお持ちですか? <a href="/login">サインイン</a></span>
</SignUp.GotoSignIn>
</SignUp>
</div>
</div>
)
}

📚 詳細: サインインブロックサインアップブロックのドキュメントで高度なカスタマイズについてご覧ください。

ステップ7: 商品ページ

src/pages/ProductsPage.tsxを作成:

import React, { useState, ComponentProps } 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<ComponentProps<typeof ListProductsGrid.Items.GridCard>[]>([
{
title: 'モダンなラップトップ',
subtitle: 'プロフェッショナル向け高性能ラップトップ',
imageUrl: 'https://images.unsplash.com/photo-1496181133206-80ce9b88a853?w=300',
chips: [{ label: '新着' }],
tags: [{ icon: 'shopping_bag' as const, label: '電子機器' }],
linkProps: { href: '/products/1', onNavigate: () => console.log('商品1を表示') }
},
{
title: 'ワイヤレスヘッドフォン',
subtitle: 'ノイズキャンセリング機能付きプレミアムワイヤレスヘッドフォン',
imageUrl: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=300',
chips: [{ label: 'セール' }],
tags: [{ icon: 'shopping_bag' as const, label: '電子機器' }],
linkProps: { href: '/products/2', onNavigate: () => console.log('商品2を表示') }
}
])

const categoryOptions = [
{ value: 'electronics', label: '電子機器' },
{ value: 'clothing', label: '衣類' },
{ value: 'home', label: 'ホーム&ガーデン' },
{ value: 'books', label: '書籍' },
{ value: 'sports', label: 'スポーツ&レクリエーション' }
]

return (
<div className="products-page">
<div className="container">
<div className="page-header">
<h1>商品管理</h1>
<button
className="btn-primary"
onClick={() => setShowCreateForm(!showCreateForm)}>
{showCreateForm ? '商品を表示' : '商品を追加'}
</button>
</div>

{showCreateForm ? (
<div className="create-product-section">
<CreateProduct
categoryOptions={categoryOptions}
onSubmit={(formData) => {
console.log('商品作成中:', formData)
const newProduct = {
title: formData.name || '',
subtitle: formData.description || '',
imageUrl: 'https://via.placeholder.com/300',
chips: [{ label: '新着' }],
tags: [{ icon: 'shopping_bag' as const, label: String(formData.category) || '一般' }],
linkProps: { href: `/products/${Date.now()}`, onNavigate: () => {} }
}
setProducts([...products, newProduct])
setShowCreateForm(false)
}}
onChange={(setError, getValues, clearErrors) => {
const values = getValues()
// 必要に応じてバリデーションロジックを追加
}}
onAcceptAttachment={(files) => console.log('ファイル受信:', files)}
onClearAttachment={() => console.log('添付ファイルクリア')}>
<CreateProduct.MainInfo>
<CreateProduct.MainInfo.SectionTitle>商品情報</CreateProduct.MainInfo.SectionTitle>
<CreateProduct.MainInfo.Dropzone />
<CreateProduct.MainInfo.NameField />
</CreateProduct.MainInfo>

<CreateProduct.BasicInfo>
<CreateProduct.BasicInfo.Title />
<CreateProduct.BasicInfo.CategoryField />
<CreateProduct.BasicInfo.DescriptionField />
</CreateProduct.BasicInfo>

<CreateProduct.Actions>
<CreateProduct.Actions.SubmitButton>商品を作成</CreateProduct.Actions.SubmitButton>
</CreateProduct.Actions>
</CreateProduct>
</div>
) : (
<div className="products-list-section">
<ListProductsGrid listProductsGridTitle="注目商品" subtitle="最新コレクション">
<ListProductsGrid.Title />
<ListProductsGrid.Items>
{products.map((product, index) => (
<ListProductsGrid.Items.GridCard key={index} {...product} />
))}
</ListProductsGrid.Items>
</ListProductsGrid>
</div>
)}
</div>
</div>
)
}

📚 詳細: 商品作成ブロック商品リストグリッドブロックで高度なカスタマイズについてご覧ください。


🎨 アプリケーションのスタイリング

ステップ8: カスタムスタイルの追加

src/App.cssを更新:

/* リセットとベーススタイル */
* {
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 {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}

/* ナビゲーションスタイル */
.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;
}

/* ページスタイル */
.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-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 {
padding: 2rem 0;
}

.page-header {
display: flex;
justify-content: space-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-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;
}

/* レスポンシブデザイン */
@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;
}
}

🔧 環境設定

ステップ9: 環境変数

プロジェクトルートに.envファイルを作成:

# APIエンドポイント
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

# アプリケーション設定
VITE_APP_NAME=MyNodeBlocksApp
VITE_APP_VERSION=1.0.0

開発用に.env.developmentを作成:

# 開発用APIエンドポイント
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

🚀 アプリケーションの実行

ステップ10: 開発サーバーの起動

# 開発サーバーを起動
npm run dev

# アプリケーションは以下で利用可能になります:
# http://localhost:3000

ステップ11: 本番ビルド

# 本番用ビルド
npm run build

# 本番ビルドをプレビュー
npm run preview

📈 高度な機能

さらなるブロックの追加

アプリケーションが成長するにつれて、より専門的なブロックを追加できます:

# 組織管理
npm install @nodeblocks/frontend-list-organizations-block

# 注文管理
npm install @nodeblocks/frontend-list-order-table-block

# チャット機能
npm install @nodeblocks/frontend-chat-conversation-block@0.2.0
npm install @nodeblocks/frontend-chat-conversation-list-block

# 高度なUIコンポーネント
npm install @nodeblocks/frontend-filter-search-panel-block@0.3.0
npm install @nodeblocks/frontend-breadcrumb-block
npm install @nodeblocks/frontend-side-navigation-block@0.3.0

カスタムブロックオーバーライド

すべてのNodeblocksコンポーネントは強力なブロックオーバーライドパターンをサポートしています:

<SignIn onSubmit={handleSubmit} onChange={handleChange}>
{({ defaultBlocks, defaultBlockOrder }) => ({
blocks: {
...defaultBlocks,
// 特定のブロックをオーバーライド
signInTitle: {
...defaultBlocks.signInTitle,
props: {
...defaultBlocks.signInTitle.props,
children: 'マイアプリへようこそ',
sx: { color: 'primary.main', fontWeight: 'bold' }
}
},
emailField: {
...defaultBlocks.emailField,
props: {
...defaultBlocks.emailField.props,
label: '会社メール',
placeholder: 'name@company.com'
}
}
},
blockOrder: defaultBlockOrder
})}
</SignIn>

詳細を学ぶ: 包括的な例と高度なブロックオーバーライド技術については、高度な使用法ガイドをご覧ください。


🔗 有用なリソース


最大の開発速度とパフォーマンスのためにNodeblocksとViteを使用して❤️で構築