· Nacho Coll · Guides  · 21 分で読了

フロントエンドを IPFS にデプロイする方法 — 分散型アプリホスティング

チュートリアル:React または静的フロントエンドを IPFS にデプロイ。IPFS.NINJA を使用してアセットをピン留めし、専用ゲートウェイ経由で提供。

チュートリアル:React または静的フロントエンドを IPFS にデプロイ。IPFS.NINJA を使用してアセットをピン留めし、専用ゲートウェイ経由で提供。

従来のウェブホスティングは、ダウンしたり、検閲されたり、維持費が高くなる可能性のある中央集権型サーバーに依存しています。あなたのフロントエンドが、よりレジリエントで、コスト効率が高く、グローバルに分散された分散型ネットワークでホストできたらどうでしょうか?IPFS ホスティングに入りましょう — InterPlanetary File System を活用した、真の分散型アプリホスティングのための、ウェブアプリケーションのデプロイへの革新的アプローチです。

この包括的なガイドでは、IPFS.NINJA を使用して React アプリケーションを IPFS にデプロイする完全なプロセスを、アプリのビルドからカスタムゲートウェイの設定、ENS ドメイン統合の探索まで進めていきます。シンプルなポートフォリオサイトを構築するにせよ、複雑な分散型アプリケーションを構築するにせよ、このチュートリアルでは、フロントエンドを効果的に IPFS にホストするための基礎を提供します。

IPFS Ninja

なぜフロントエンドホスティングに IPFS を選ぶのか?

技術的な実装に入る前に、IPFS ホスティングが世界中の開発者や組織の間で注目されている理由を理解しましょう。

分散化の利点:従来のホスティングでは、サイトが単一のサーバーや CDN に依存しているのに対し、IPFS はあなたのコンテンツをノードのグローバルネットワーク全体に分散します。これは、個々のノードがオフラインになっても、あなたのサイトはアクセス可能なままであることを意味します。

コンテンツアドレッシング:IPFS は暗号化ハッシュ(CID)を使用してコンテンツを識別し、不変性を保証します。フロントエンドをデプロイすると、ユーザーはコンテンツが改ざんされていないことを信頼できます。

コスト効率:IPFS.NINJA のようなサービスを使用すると、従来のホスティングコストのほんの一部で静的フロントエンドをホストできます。当社の Dharma プランは無料で 1 GB のストレージと月 2 GB の帯域幅を提供し、Bodhi プランは月 5 ドルで 10 GB のストレージと 20 GB の帯域幅を提供し、Karma プランは月 19 ドルで 100 GB / 100 GB にステップアップします。

パフォーマンス:IPFS のピアツーピアの性質は、コンテンツが最も近い利用可能なノードから提供されることを意味し、集中型 CDN よりも優れたパフォーマンスを提供する可能性があります。

検閲耐性:分散型ホスティングにより、単一のエンティティがあなたのアプリケーションを停止することは極めて困難になります。

ウェブホスティングのための IPFS ピン留めを理解する

フロントエンドを IPFS にデプロイする場合、本質的には、ビルドされたアプリケーションファイルをネットワークにアップロードすることになります。ただし、IPFS ノードは、積極的に使用しているコンテンツのみを保持します。これが IPFS ピン留めが重要になる理由です — IPFS.NINJA のような専用ピン留めサービスにファイルを保存しておくことで、ファイルが利用可能な状態を維持することを保証します。

ピン留めをホスティング保証と考えてください。あなたのファイルは IPFS ネットワーク上で一意のコンテンツ識別子(CID)とともに存在しますが、ピン留めサービスにより、サイトを訪問しようとするユーザーが常にアクセスできるようになります。

開発環境のセットアップ

IPFS にデプロイする React アプリケーションの作成から始めましょう。既存のフロントエンドプロジェクトがある場合は、ビルド最適化セクションにスキップできます。

# 新しい React アプリを作成
npx create-react-app my-ipfs-app
cd my-ipfs-app

# IPFS 統合のために追加の依存関係をインストール
npm install axios

このチュートリアルでは、分散型アプリケーションでよく必要とされるさまざまな機能を示す、シンプルだが機能的な React アプリケーションを作成します。

// src/App.js
import React, { useState, useEffect } from 'react';
import './App.css';

function App() {
  const [ipfsStatus, setIpfsStatus] = useState('Checking...');
  const [deploymentInfo, setDeploymentInfo] = useState(null);

  useEffect(() => {
    // IPFS 接続のチェックをシミュレート
    setTimeout(() => {
      setIpfsStatus('Connected to IPFS Network');
      setDeploymentInfo({
        cid: 'QmYourAppHashWillAppearHere',
        deployed: new Date().toISOString(),
        size: '2.3 MB'
      });
    }, 2000);
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        <h1>🚀 My Decentralized App</h1>
        <p>Hosted on IPFS via IPFS.NINJA</p>
        
        <div className="status-card">
          <h3>IPFS Status</h3>
          <p className={ipfsStatus.includes('Connected') ? 'connected' : 'checking'}>
            {ipfsStatus}
          </p>
        </div>

        {deploymentInfo && (
          <div className="deployment-info">
            <h3>Deployment Information</h3>
            <p><strong>CID:</strong> {deploymentInfo.cid}</p>
            <p><strong>Size:</strong> {deploymentInfo.size}</p>
            <p><strong>Deployed:</strong> {deploymentInfo.deployed}</p>
          </div>
        )}

        <div className="features">
          <h3>Decentralized Features</h3>
          <ul>
            <li>✅ Censorship Resistant</li>
            <li>✅ Globally Distributed</li>
            <li>✅ Content Addressable</li>
            <li>✅ Cost Effective</li>
          </ul>
        </div>
      </header>
    </div>
  );
}

export default App;

視覚的に魅力的にするためのスタイルを追加します:

/* src/App.css */
.App {
  text-align: center;
}

.App-header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  padding: 40px;
  color: white;
  min-height: 100vh;
}

.status-card, .deployment-info, .features {
  background: rgba(255, 255, 255, 0.1);
  border-radius: 10px;
  padding: 20px;
  margin: 20px auto;
  max-width: 500px;
  backdrop-filter: blur(10px);
}

.connected {
  color: #4ade80;
  font-weight: bold;
}

.checking {
  color: #fbbf24;
}

.features ul {
  list-style: none;
  padding: 0;
}

.features li {
  padding: 8px 0;
  font-size: 1.1em;
}

IPFS 用にビルドを最適化する

IPFS は静的ファイルで最適に機能するため、分散型ホスティング用に React アプリケーションが正しくビルドされるようにする必要があります。いくつかの重要な考慮事項があります:

1. IPFS パス用に React を設定する

デフォルトでは、React アプリはルートドメインから提供されると想定しています。IPFS でホストする場合、アプリは https://gateway.ipfs.io/ipfs/QmYourHash のようなパスでアクセスされます。これを正しく処理するように React を設定する必要があります。

package.json を作成または変更します:

{
  "name": "my-ipfs-app",
  "version": "0.1.0",
  "homepage": "./",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "axios": "^1.6.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "ipfs-build": "npm run build && npm run prepare-ipfs",
    "prepare-ipfs": "echo 'Build optimized for IPFS deployment'"
  }
}

"homepage": "./" 設定は、React に相対パスを使用するよう指示します。これは IPFS ホスティングに不可欠です。

2. シングルページアプリケーションのルーティングを処理する

アプリが React Router を使用している場合、IPFS との互換性のために BrowserRouter ではなく HashRouter を使用する必要があります:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { HashRouter } from 'react-router-dom';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <HashRouter>
      <App />
    </HashRouter>
  </React.StrictMode>
);

3. アプリケーションをビルドする

それでは本番ビルドを作成しましょう:

npm run build

これにより、アプリケーションに必要なすべての静的ファイルを含む build フォルダが生成されます。

IPFS.NINJA を使用して IPFS にアップロードする

エキサイティングな部分が来ました — IPFS.NINJA の強力なピン留めサービスを使用してフロントエンドを IPFS にデプロイすることです。他のピン留めサービスと比較して、IPFS.NINJA は競争力のある価格設定、専用ゲートウェイ、開発者フレンドリーな API を提供します。

IPFS.NINJA アカウントのセットアップ

まず、無料の IPFS.NINJA アカウントを作成して API キーを取得します。bws_ の後に 32 個の 16 進文字が続く形式の API キーを受け取ります。

方法 1:ダッシュボード経由でアップロード

フロントエンドをデプロイする最も簡単な方法は、IPFS.NINJA ダッシュボード経由です:

  1. https://ipfs.ninja に移動してログイン
  2. アップロードセクションに移動
  3. プロジェクト用に新しいフォルダを作成
  4. build フォルダ全体をドラッグ&ドロップ
  5. “My React App v1.0” のような説明を追加
  6. アップロードをクリック

ダッシュボードがファイルを処理し、アプリケーション全体を表す CID を返します。

方法 2:API 経由でプログラム的にアップロード

より自動化されたデプロイプロセスのために、IPFS.NINJA API を使用できます。これは CI/CD パイプラインに特に便利です。

デプロイスクリプトを作成します:

// deploy.js
const fs = require('fs');
const path = require('path');
const axios = require('axios');

const API_KEY = 'bws_your_32_character_hex_api_key_here';
const API_BASE = 'https://api.ipfs.ninja';

async function uploadFile(filePath, fileName) {
  try {
    const fileContent = fs.readFileSync(filePath);
    const base64Content = fileContent.toString('base64');
    
    const response = await axios.post(`${API_BASE}/upload/new`, {
      content: base64Content,
      description: `Frontend file: ${fileName}`,
      metadata: {
        filename: fileName,
        type: 'frontend-asset',
        deployment: new Date().toISOString()
      }
    }, {
      headers: {
        'X-Api-Key': API_KEY,
        'Content-Type': 'application/json'
      }
    });
    
    return response.data;
  } catch (error) {
    console.error(`Failed to upload ${fileName}:`, error.response?.data || error.message);
    throw error;
  }
}

async function uploadDirectory(dirPath) {
  const files = fs.readdirSync(dirPath);
  const uploads = [];
  
  for (const file of files) {
    const fullPath = path.join(dirPath, file);
    const stat = fs.statSync(fullPath);
    
    if (stat.isDirectory()) {
      // サブディレクトリを再帰的にアップロード
      const subUploads = await uploadDirectory(fullPath);
      uploads.push(...subUploads);
    } else {
      console.log(`Uploading ${file}...`);
      const result = await uploadFile(fullPath, file);
      uploads.push({
        file: file,
        cid: result.cid,
        url: result.uris.url,
        size: result.sizeMB
      });
    }
  }
  
  return uploads;
}

async function deployFrontend() {
  try {
    console.log('🚀 Starting IPFS deployment...');
    
    const buildPath = './build';
    if (!fs.existsSync(buildPath)) {
      throw new Error('Build directory not found. Run "npm run build" first.');
    }
    
    const uploads = await uploadDirectory(buildPath);
    
    console.log('\n✅ Deployment complete!');
    console.log('\nUploaded files:');
    uploads.forEach(upload => {
      console.log(`📄 ${upload.file}: ${upload.cid} (${upload.size}MB)`);
    });
    
    // メイン HTML ファイルを検索
    const indexUpload = uploads.find(u => u.file === 'index.html');
    if (indexUpload) {
      console.log(`\n🌐 Your app is live at: ${indexUpload.url}`);
      console.log(`📋 Root CID: ${indexUpload.cid}`);
    }
    
  } catch (error) {
    console.error('❌ Deployment failed:', error.message);
    process.exit(1);
  }
}

// デプロイを実行
deployFrontend();

このスクリプトを package.json に追加します:

{
  "scripts": {
    "deploy": "node deploy.js",
    "build-and-deploy": "npm run build && npm run deploy"
  }
}

デプロイを実行:

npm run build-and-deploy

カスタムゲートウェイの設定

IPFS.NINJA の優れた機能の 1 つは、アプリケーション用のカスタムゲートウェイを作成できることです。これにより、パブリックゲートウェイと比較してより良いブランディングとパフォーマンスが提供されます。

専用ゲートウェイの作成

IPFS.NINJA ダッシュボード経由:

  1. Gateways セクションに移動
  2. “Create New Gateway” をクリック
  3. サブドメインを選択(例:myapp.gw.ipfs.ninja
  4. アクセス設定を構成:
    • Open:公開アクセス可能
    • Restricted:API キーが必要
    • Folder:ディレクトリリストを提供
  5. 必要に応じてオリジン制限を設定
  6. 追加のセキュリティのために IP ホワイトリストを設定

ゲートウェイ設定例

// gateway-config.js
const gatewayConfig = {
  slug: 'myapp',
  access: 'open',
  description: 'My React App Gateway',
  settings: {
    cors: true,
    caching: true,
    compression: true
  }
};

async function setupGateway() {
  try {
    const response = await axios.post(`${API_BASE}/gateways`, gatewayConfig, {
      headers: {
        'X-Api-Key': API_KEY,
        'Content-Type': 'application/json'
      }
    });
    
    console.log('Gateway created:', response.data);
    console.log(`Access your app at: https://${gatewayConfig.slug}.gw.ipfs.ninja/ipfs/${YOUR_APP_CID}`);
  } catch (error) {
    console.error('Gateway setup failed:', error.response?.data);
  }
}

カスタムゲートウェイの利点

  • ブランド URL:一般的な IPFS ゲートウェイの代わりに、独自のサブドメインを使用
  • パフォーマンスの向上:アプリケーション専用のリソース
  • 分析:使用状況とパフォーマンスメトリクスを追跡
  • セキュリティ:API キーまたは IP 制限でアクセスを制御
  • 信頼性:パブリックゲートウェイの可用性への依存を削減

高度な IPFS ホスティング戦略

1. コンテンツ更新の実装

ファイルを単純に上書きできる従来のホスティングとは異なり、IPFS コンテンツは不変です。各変更により新しい CID が作成されます。更新を効果的に処理する方法は次のとおりです:

// update-manager.js
class IPFSUpdateManager {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.apiBase = 'https://api.ipfs.ninja';
  }
  
  async deployUpdate(buildPath, version) {
    // 新しいバージョンをアップロード
    const newCID = await this.uploadBuild(buildPath, version);
    
    // バージョンマッピングを更新
    await this.updateVersionMapping(version, newCID);
    
    // オプションで ENS レコードを更新(詳細は後述)
    // await this.updateENSRecord(newCID);
    
    return newCID;
  }
  
  async updateVersionMapping(version, cid) {
    const versionInfo = {
      version,
      cid,
      timestamp: new Date().toISOString(),
      description: `Release ${version}`
    };
    
    await axios.post(`${this.apiBase}/upload/new`, {
      content: Buffer.from(JSON.stringify(versionInfo)).toString('base64'),
      description: `Version manifest ${version}`,
      metadata: { type: 'version-manifest', version }
    }, {
      headers: { 'X-Api-Key': this.apiKey }
    });
  }
}

2. パフォーマンスの最適化

// optimization.js
const compressionOptions = {
  // バンドルサイズを最小化
  build: {
    optimization: {
      splitChunks: {
        chunks: 'all',
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all',
          }
        }
      }
    }
  },
  
  // IPFS 固有の最適化
  ipfs: {
    chunkSize: '1MB', // IPFS の最適なチャンクサイズ
    preloadCriticalAssets: true,
    enableServiceWorker: true
  }
};

3. オフラインサポートのための Service Worker の追加

アプリをオフライン使用のためにキャッシュする service worker を作成します:

// public/sw.js
const CACHE_NAME = 'ipfs-app-v1';
const urlsToCache = [
  '/',
  '/static/js/bundle.js',
  '/static/css/main.css',
  '/manifest.json'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // キャッシュバージョンを返すか、ネットワークから取得
        return response || fetch(event.request);
      })
  );
});

React アプリで service worker を登録します:

// src/serviceWorkerRegistration.js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js')
      .then(registration => {
        console.log('SW registered: ', registration);
      })
      .catch(registrationError => {
        console.log('SW registration failed: ', registrationError);
      });
  });
}

ENS 統合とカスタムドメイン

Ethereum Name Service(ENS)を使用すると、人間が読めるドメイン名を IPFS コンテンツにマッピングできます。これにより、分散型コンテンツを指す従来のドメインを持つことができる強力な組み合わせが生まれます。

IPFS サイト用に ENS をセットアップ

  1. ENS ドメインの登録ENS マネージャーを使用して .eth ドメインを登録
  2. コンテンツハッシュの設定:ENS ドメインを IPFS CID に向ける
  3. DNS ブリッジの構成:従来のドメインの場合は、DNS レコードを設定
// ens-integration.js
async function updateENSRecord(domain, ipfsCid) {
  // これは通常、web3 プロバイダーを使用
  // 説明のための簡略化された例
  const ensContract = new ethers.Contract(ENS_REGISTRY_ADDRESS, ENS_ABI, signer);
  
  try {
    const tx = await ensContract.setContentHash(
      ethers.utils.namehash(domain),
      `ipfs://${ipfsCid}`
    );
    
    await tx.wait();
    console.log(`ENS record updated: ${domain} -> ${ipfsCid}`);
  } catch (error) {
    console.error('ENS update failed:', error);
  }
}

従来のドメイン統合

従来の DNS を IPFS と一緒に使用することもできます:

; example.com の DNS TXT レコード
_dnslink.example.com. IN TXT "dnslink=/ipfs/QmYourContentHash"

多くの DNS プロバイダーがこれをサポートしており、ユーザーが通常のドメイン名経由で IPFS ホストされたサイトにアクセスできるようになります。

監視と分析

IPFS ホストされたフロントエンドがどのように機能するかを理解することは重要です。IPFS.NINJA はピン留めされたコンテンツに対する包括的な分析を提供します。

IPFS.NINJA Analytics API の使用

// analytics.js
async function getDeploymentAnalytics(cid) {
  try {
    const response = await axios.get(`${API_BASE}/analytics/files/${cid}`, {
      headers: { 'X-Api-Key': API_KEY }
    });
    
    const analytics = response.data;
    
    console.log('📊 Analytics Report:');
    console.log(`Total Requests: ${analytics.totalRequests}`);
    console.log(`Bandwidth Used: ${analytics.bandwidthGB}GB`);
    console.log(`Geographic Distribution:`, analytics.geoStats);
    
    return analytics;
  } catch (error) {
    console.error('Failed to fetch analytics:', error);
  }
}

// 監視ダッシュボードをセットアップ
async function createMonitoringDashboard() {
  const analytics = await getDeploymentAnalytics(YOUR_APP_CID);
  
  // ビジュアルダッシュボードを作成(お好みのチャートライブラリと統合)
  const dashboardData = {
    requestsOverTime: analytics.dailyRequests,
    popularAssets: analytics.assetStats,
    performanceMetrics: {
      averageResponseTime: analytics.avgResponseTime,
      cacheHitRatio: analytics.cacheHitRatio
    }
  };
  
  return dashboardData;
}

IPFS フロントエンドホスティングのベストプラクティス

1. コンテンツの整理

最適な IPFS パフォーマンスのためにビルドを構造化します:

build/
├── index.html (エントリーポイント)
├── static/
│   ├── js/
│   │   ├── main.[hash].js
│   │   └── vendor.[hash].js
│   ├── css/
│   │   └── main.[hash].css
│   └── media/
│       └── images/
└── manifest.json

2. セキュリティの考慮事項

  • コンテンツの整合性:IPFS の暗号化ハッシュにより、コンテンツの整合性が保証されます
  • アクセス制御:プライベートデプロイメントには IPFS.NINJA のゲートウェイ制限を使用
  • HTTPS:コンテンツには常に HTTPS ゲートウェイ経由でアクセス
  • 定期更新:依存関係を最新に保ち、必要に応じて再デプロイ

3. パフォーマンスの最適化

// performance-tips.js
const performanceOptimizations = {
  // より良いキャッシュのためのバンドル分割
  splitChunks: true,
  
  // 重要なリソースをプリロード
  preloadStrategy: 'critical-path',
  
  // 画像を最適化
  imageOptimization: {
    format: 'webp',
    compression: 0.8,
    lazy: true
  },
  
  // 圧縮を有効化
  compression: 'gzip',
  
  // 一般的なライブラリには CDN を使用
  externals: {
    react: 'React',
    'react-dom': 'ReactDOM'
  }
};

一般的な問題のトラブルシューティング

コンテンツが読み込まれない

IPFS コンテンツが読み込まれない場合:

  1. CID の有効性を確認:CID が正しいことを確認
  2. ゲートウェイの可用性:別のゲートウェイを試す
  3. ピン留め状態:ファイルが適切にピン留めされていることを確認
  4. ネットワーク接続:IPFS ネットワークの状態を確認
// troubleshooting.js
async function diagnosticCheck(cid) {
  const gateways = [
    'https://ipfs.io/ipfs/',
    'https://gateway.pinata.cloud/ipfs/',
    'https://cloudflare-ipfs.com/ipfs/'
  ];
  
  const results = await Promise.allSettled(
    gateways.map(gateway => 
      axios.get(`${gateway}${cid}`, { timeout: 5000 })
    )
  );
  
  results.forEach((result, index) => {
    const gateway = gateways[index];
    if (result.status === 'fulfilled') {
      console.log(`✅ ${gateway} - Working`);
    } else {
      console.log(`❌ ${gateway} - Failed: ${result.reason.message}`);
    }
  });
}

ビルドの問題

一般的なビルドの問題と解決策:

# React ビルドキャッシュをクリア
rm -rf build/ node_modules/.cache/

# 詳細出力で再ビルド
CI=true npm run build

# ルーティングの問題をチェック
# SPA ルーティングに HashRouter を使用していることを確認

IPFS ホスティングソリューションの比較

フロントエンドホスティング用の IPFS ピン留めサービスを選択するときは、これらの要素を考慮してください:

機能IPFS.NINJA代替品
価格無料層:1GB、月額 5 ドル:10GB大きく異なる
カスタムゲートウェイ✅ 含まれる多くの場合プレミアム機能
API の品質RESTful、文書化が充実質はまちまち
分析組み込みダッシュボードオプションが限定的
開発者ツール包括的な SDK基本的なツール

詳細な比較については、IPFS ピン留めサービスの包括的な分析およびPinata との具体的な比較を参照してください。

分散型フロントエンドホスティングの未来

ウェブホスティングの状況は急速に進化しています。IPFS は、より分散型で回復力のあるウェブインフラストラクチャへのシフトの始まりに過ぎません。注目すべき今後の発展:

  • プロトコルの改善:IPFS 2.0 およびその他の次世代プロトコル
  • ブラウザ統合:主要ブラウザでのネイティブ IPFS サポート
  • 開発ツール:分散型開発のためのより良いフレームワークとツール
  • パフォーマンスの向上:改善されたキャッシュとコンテンツ配信メカニズム

IPFS デプロイメントを開始する

フロントエンドを IPFS にデプロイする完全なプロセスを理解したので、真の分散型アプリケーションを構築する準備が整いました。IPFS の分散アーキテクチャと IPFS.NINJA の開発者フレンドリーなピン留めサービスの組み合わせは、最新のウェブアプリケーションの堅牢な基盤を提供します。

個人ポートフォリオ、ビジネスウェブサイト、または複雑な分散型アプリケーションを構築する場合でも、IPFS ホスティングは比類のない回復力、パフォーマンス、コスト効率を提供します。IPFS コンテンツの不変性とノードのグローバル分散の組み合わせにより、従来のホスティングの制限に関係なく、アプリケーションがアクセス可能であり続けることが保証されます。

ファイルのアップロードと管理に関するより詳細な情報については、包括的な IPFS アップロードチュートリアルを確認し、この強力なテクノロジーを最大限に活用するために IPFS ピン留めの仕組みについて詳しく学んでください。

ピン留めを始める準備はできましたか? 無料アカウントを作成 — 50 ファイル、1 GB ストレージ、2 GB 帯域幅/月。クレジットカード不要。

ブログに戻る