ReactでWebサイトの作り方|ゼロから公開まで完全ガイド
Reactを使ったWebサイトの作り方を初心者向けに解説。環境構築からコンポーネント作成、ルーティング、デプロイまで実際のコード例とともに詳しく説明します。
みなさん、「ReactでWebサイトを作ってみたい!」と思ったことはありませんか?
「どこから始めたらいいの?」 「環境構築って難しそう...」 「実際に動くサイトを作ってみたい!」
こんな風に感じている方も多いですよね。
でも大丈夫です! この記事では、React初心者でも安心してWebサイトを作れる方法をお伝えします。
環境構築から始まって、最終的にはインターネットに公開するまで。 一緒に、ステップバイステップで進んでいきましょう。
実際に手を動かしながら学べるので、きっと楽しく進められるはずです。
ReactでWebサイトを作る全体の流れ
まず、どんな手順でサイトを作っていくのか、全体像を見てみましょう。
開発の流れはこんな感じです
Reactでサイトを作る時は、いくつかのステップに分けて進めます。
1. 環境構築・プロジェクト作成
↓
2. ページ構成の設計
↓
3. 基本レイアウトの作成
↓
4. コンポーネントの実装
↓
5. ルーティングの設定
↓
6. スタイリングの適用
↓
7. ビルドとデプロイ
↓
8. サイト公開
最初は複雑そうに見えるかもしれませんね。 でも一つずつ進めていけば、意外とシンプルなんです。
今回作るサイトについて
この記事では、ポートフォリオサイトを作ってみましょう。
どんなページを作るかというと...
ページ構成
- ホームページ(メインページ)
- 自己紹介ページ
- 作品一覧ページ
- お問い合わせページ
使う技術
- React(メインの技術)
- React Router(ページを切り替える技術)
- CSS Modules(見た目を整える技術)
- Vercel(サイトを公開する技術)
どれも実際の現場でよく使われる技術です。 一度覚えれば、他のプロジェクトでも活用できますよ。
事前に知っておくといいこと
Reactを始める前に、これくらいは知っておきましょう。
// HTML/CSS/JavaScriptの基本知識があると安心です
// 例:このようなコードが理解できるレベル
// JavaScript ES6の基本文法
const greeting = (name) => {
return `Hello, ${name}!`;
};
// DOM操作の基本
document.getElementById('app').innerHTML = greeting('React');
この程度のコードが読めれば、全然問題ありません。 もしわからないところがあっても、この記事で一緒に学んでいきましょう。
環境構築:ReactでWebサイトを作る準備をしよう
それでは、実際にReactを始める準備をしていきましょう。
必要なツールをインストールしよう
まず、ReactでWebサイトを作るために必要なツールを準備します。
# Node.jsのバージョン確認(16.0.0以上が推奨)
node --version
npm --version
# Node.jsがインストールされていない場合
# https://nodejs.org/ からダウンロードしてインストール
Node.jsは、JavaScriptを実行するためのツールです。 ReactでWebサイトを作るには、必ず必要になります。
もしNode.jsがインストールされていなければ、公式サイトからダウンロードしてくださいね。
Create React Appでプロジェクトを作ろう
Node.jsの準備ができたら、次はReactプロジェクトを作りましょう。
# Reactプロジェクトの作成
npx create-react-app my-portfolio-site
cd my-portfolio-site
# 開発サーバーの起動
npm start
# ブラウザで http://localhost:3000 を確認
Create React Appは、Reactプロジェクトを簡単に作ってくれるツールです。 面倒な設定を自動でやってくれるので、とても便利なんです。
npm start
を実行すると、ブラウザでサイトが表示されます。
最初は、Reactのロゴがくるくる回っているページが見えるはずです。
プロジェクトの中身を確認してみよう
作成されたプロジェクトには、こんなファイルが入っています。
my-portfolio-site/
├── public/
│ ├── index.html
│ ├── favicon.ico
│ └── manifest.json
├── src/
│ ├── App.js
│ ├── App.css
│ ├── index.js
│ ├── index.css
│ └── logo.svg
├── package.json
└── README.md
最初はたくさんファイルがあって驚くかもしれませんね。
でも大丈夫です。基本的にはsrc
フォルダの中だけ触れば十分です。
src/App.js
がメインのファイルになります。
ここを編集することで、サイトの内容を変更できますよ。
追加で必要なライブラリをインストールしよう
ポートフォリオサイトを作るために、いくつかライブラリを追加しましょう。
# React Routerのインストール(ページ遷移用)
npm install react-router-dom
# アイコンライブラリのインストール
npm install react-icons
# 開発効率化のためのツール
npm install -D prettier eslint-config-prettier
それぞれ、どんな役割があるか説明しますね。
- React Router:複数のページを作るときに使います
- React Icons:きれいなアイコンを簡単に使えます
- Prettier:コードを自動で整理してくれます
ライブラリをインストールすることで、開発がグッと楽になります。
基本レイアウト:サイトの骨格を作ろう
次は、サイト全体の構造を作っていきましょう。
ディレクトリ構造を整理しよう
まず、ファイルを整理するためのフォルダを作ります。
# srcディレクトリ内に新しいフォルダを作成
mkdir src/components
mkdir src/pages
mkdir src/styles
mkdir src/assets
作成後のフォルダ構成はこんな感じになります。
src/
├── components/ # 再利用可能なコンポーネント
│ ├── Header/
│ ├── Footer/
│ ├── Layout/
│ └── Card/
├── pages/ # ページコンポーネント
│ ├── Home/
│ ├── About/
│ ├── Works/
│ └── Contact/
├── styles/ # スタイルファイル
│ ├── global.css
│ └── variables.css
└── assets/ # 画像などの静的ファイル
└── images/
これで、ファイルがどこにあるか分かりやすくなりました。 大きなサイトを作るときも、この構造で進めれば迷いません。
レイアウトコンポーネントを作ろう
サイト全体の基本的な構造を作ってみましょう。
// src/components/Layout/Layout.js
import React from 'react';
import Header from '../Header/Header';
import Footer from '../Footer/Footer';
import styles from './Layout.module.css';
function Layout({ children }) {
return (
<div className={styles.layout}>
<Header />
<main className={styles.main}>
{children}
</main>
<Footer />
</div>
);
}
export default Layout;
このコードは、サイト全体の枠組みを作っています。
どのページを見ても、ヘッダーとフッターは同じものが表示されます。
変わるのは、真ん中のmain
部分だけです。
{children}
の部分に、各ページの内容が入ります。
とても便利な仕組みなんです。
次に、スタイルも作ってみましょう。
/* src/components/Layout/Layout.module.css */
.layout {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.main {
flex: 1;
padding: 2rem 1rem;
max-width: 1200px;
margin: 0 auto;
width: 100%;
}
@media (max-width: 768px) {
.main {
padding: 1rem 0.5rem;
}
}
min-height: 100vh
で、画面の高さいっぱいにレイアウトを広げています。
flex: 1
で、メインコンテンツが残りのスペースを使います。
スマホでも見やすいように、@media
でモバイル対応もしています。
ヘッダーコンポーネントを作ろう
サイトの上部に表示されるヘッダーを作りましょう。
// src/components/Header/Header.js
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { FaBars, FaTimes } from 'react-icons/fa';
import styles from './Header.module.css';
function Header() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const toggleMenu = () => {
setIsMenuOpen(!isMenuOpen);
};
const closeMenu = () => {
setIsMenuOpen(false);
};
return (
<header className={styles.header}>
<div className={styles.container}>
<Link to="/" className={styles.logo} onClick={closeMenu}>
<h1>My Portfolio</h1>
</Link>
<nav className={`${styles.nav} ${isMenuOpen ? styles.navOpen : ''}`}>
<ul className={styles.navList}>
<li>
<Link to="/" className={styles.navLink} onClick={closeMenu}>
ホーム
</Link>
</li>
<li>
<Link to="/about" className={styles.navLink} onClick={closeMenu}>
自己紹介
</Link>
</li>
<li>
<Link to="/works" className={styles.navLink} onClick={closeMenu}>
作品一覧
</Link>
</li>
<li>
<Link to="/contact" className={styles.navLink} onClick={closeMenu}>
お問い合わせ
</Link>
</li>
</ul>
</nav>
<button
className={styles.menuButton}
onClick={toggleMenu}
aria-label="メニューを開く"
>
{isMenuOpen ? <FaTimes /> : <FaBars />}
</button>
</div>
</header>
);
}
export default Header;
このヘッダーには、いくつかの便利な機能があります。
主な機能
- サイトロゴ(クリックするとホームに戻る)
- ナビゲーションメニュー(各ページへのリンク)
- モバイル用ハンバーガーメニュー
useState
を使って、メニューの開閉状態を管理しています。
スマホでは、ハンバーガーメニューが表示されます。
次に、ヘッダーのスタイルも作ってみましょう。
/* src/components/Header/Header.module.css */
.header {
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 100;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
display: flex;
justify-content: space-between;
align-items: center;
height: 70px;
}
.logo h1 {
margin: 0;
color: #333;
font-size: 1.5rem;
text-decoration: none;
}
.logo {
text-decoration: none;
}
.nav {
display: flex;
}
.navList {
display: flex;
list-style: none;
margin: 0;
padding: 0;
gap: 2rem;
}
.navLink {
text-decoration: none;
color: #333;
font-weight: 500;
transition: color 0.3s ease;
}
.navLink:hover {
color: #007bff;
}
.menuButton {
display: none;
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #333;
}
/* モバイル対応 */
@media (max-width: 768px) {
.menuButton {
display: block;
}
.nav {
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transform: translateY(-100%);
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.navOpen {
transform: translateY(0);
opacity: 1;
visibility: visible;
}
.navList {
flex-direction: column;
gap: 0;
padding: 1rem;
}
.navLink {
display: block;
padding: 1rem 0;
border-bottom: 1px solid #eee;
}
}
position: sticky
で、ヘッダーが常に画面の上に固定されます。
スクロールしても、いつでもメニューが使えて便利ですね。
モバイル版では、メニューがアニメーションで表示されます。
transform
とopacity
を使って、滑らかな動きを作っています。
フッターコンポーネントを作ろう
最後に、サイトの下部に表示されるフッターを作りましょう。
// src/components/Footer/Footer.js
import React from 'react';
import { FaGithub, FaTwitter, FaLinkedin } from 'react-icons/fa';
import styles from './Footer.module.css';
function Footer() {
const currentYear = new Date().getFullYear();
return (
<footer className={styles.footer}>
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.info}>
<h3>My Portfolio</h3>
<p>Webデベロッパーとして成長を続けています</p>
</div>
<div className={styles.social}>
<h4>SNS</h4>
<div className={styles.socialLinks}>
<a
href="https://github.com"
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub"
>
<FaGithub />
</a>
<a
href="https://twitter.com"
target="_blank"
rel="noopener noreferrer"
aria-label="Twitter"
>
<FaTwitter />
</a>
<a
href="https://linkedin.com"
target="_blank"
rel="noopener noreferrer"
aria-label="LinkedIn"
>
<FaLinkedin />
</a>
</div>
</div>
</div>
<div className={styles.copyright}>
<p>© {currentYear} My Portfolio. All rights reserved.</p>
</div>
</div>
</footer>
);
}
export default Footer;
フッターには、こんな情報を入れています。
フッターの内容
- サイトの簡単な説明
- SNSへのリンク
- 著作権表示
new Date().getFullYear()
で、現在の年を自動取得しています。
毎年手動で更新する必要がなくて便利ですね。
フッターのスタイルも作ってみましょう。
/* src/components/Footer/Footer.module.css */
.footer {
background-color: #333;
color: #fff;
padding: 2rem 0 1rem;
margin-top: auto;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.info h3 {
margin: 0 0 0.5rem 0;
color: #fff;
}
.info p {
margin: 0;
color: #ccc;
line-height: 1.6;
}
.social h4 {
margin: 0 0 1rem 0;
color: #fff;
}
.socialLinks {
display: flex;
gap: 1rem;
}
.socialLinks a {
color: #ccc;
font-size: 1.5rem;
transition: color 0.3s ease;
}
.socialLinks a:hover {
color: #007bff;
}
.copyright {
border-top: 1px solid #555;
padding-top: 1rem;
text-align: center;
}
.copyright p {
margin: 0;
color: #ccc;
font-size: 0.9rem;
}
@media (max-width: 768px) {
.content {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.social {
text-align: center;
}
}
暗い背景色で、ヘッダーとは対照的なデザインにしています。 SNSアイコンは、ホバーすると色が変わるようにしました。
これで、サイトの基本的な構造ができあがりました。
ページコンポーネント:各ページを作ってみよう
いよいよ、サイトの各ページを作っていきましょう。
ホームページを作ろう
まずは、サイトの顔となるホームページから作ります。
// src/pages/Home/Home.js
import React from 'react';
import { Link } from 'react-router-dom';
import { FaArrowRight, FaCode, FaDesktop, FaMobile } from 'react-icons/fa';
import styles from './Home.module.css';
function Home() {
const skills = [
{
icon: <FaCode />,
title: 'フロントエンド開発',
description: 'React, JavaScript, HTML/CSSを使用したモダンなWebアプリケーション開発'
},
{
icon: <FaDesktop />,
title: 'Webサイト制作',
description: 'レスポンシブデザインに対応した美しく機能的なWebサイトの制作'
},
{
icon: <FaMobile />,
title: 'モバイル対応',
description: 'あらゆるデバイスで快適に利用できるモバイルファーストな設計'
}
];
return (
<div className={styles.home}>
{/* ヒーローセクション */}
<section className={styles.hero}>
<div className={styles.heroContent}>
<h1 className={styles.heroTitle}>
こんにちは!<br />
Webデベロッパーの<br />
<span className={styles.highlight}>田中太郎</span>です
</h1>
<p className={styles.heroDescription}>
Reactを中心としたモダンなWeb技術で、
使いやすく美しいWebサイトとアプリケーションを作成しています。
</p>
<div className={styles.heroButtons}>
<Link to="/works" className={styles.primaryButton}>
作品を見る
<FaArrowRight />
</Link>
<Link to="/about" className={styles.secondaryButton}>
自己紹介
</Link>
</div>
</div>
<div className={styles.heroImage}>
<div className={styles.placeholder}>
{/* ここにプロフィール画像やイラストを配置 */}
<FaCode size={80} />
</div>
</div>
</section>
{/* スキルセクション */}
<section className={styles.skills}>
<div className={styles.container}>
<h2 className={styles.sectionTitle}>できること</h2>
<div className={styles.skillsGrid}>
{skills.map((skill, index) => (
<div key={index} className={styles.skillCard}>
<div className={styles.skillIcon}>
{skill.icon}
</div>
<h3>{skill.title}</h3>
<p>{skill.description}</p>
</div>
))}
</div>
</div>
</section>
{/* CTAセクション */}
<section className={styles.cta}>
<div className={styles.container}>
<h2>お仕事のご相談はこちら</h2>
<p>プロジェクトのご相談やお見積もりなど、お気軽にお問い合わせください。</p>
<Link to="/contact" className={styles.ctaButton}>
お問い合わせ
<FaArrowRight />
</Link>
</div>
</section>
</div>
);
}
export default Home;
ホームページは、こんな構成になっています。
ヒーローセクション 自己紹介とメインメッセージを表示します。 訪問者が最初に見る重要な部分ですね。
スキルセクション できることを3つのカードで紹介します。 アイコンを使うことで、視覚的にわかりやすくしています。
CTAセクション お問い合わせへ誘導するセクションです。 CTAは「Call To Action」の略で、行動を促す部分のことです。
次に、ホームページのスタイルを作ってみましょう。
/* src/pages/Home/Home.module.css */
.home {
width: 100%;
}
/* ヒーローセクション */
.hero {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
align-items: center;
padding: 4rem 0;
min-height: 60vh;
}
.heroContent {
max-width: 100%;
}
.heroTitle {
font-size: 2.5rem;
font-weight: bold;
line-height: 1.2;
margin: 0 0 1.5rem 0;
color: #333;
}
.highlight {
color: #007bff;
}
.heroDescription {
font-size: 1.2rem;
line-height: 1.6;
color: #666;
margin: 0 0 2rem 0;
}
.heroButtons {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.primaryButton {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 1rem 2rem;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
transition: all 0.3s ease;
}
.primaryButton:hover {
background-color: #0056b3;
transform: translateY(-2px);
}
.secondaryButton {
display: inline-flex;
align-items: center;
padding: 1rem 2rem;
border: 2px solid #007bff;
color: #007bff;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
transition: all 0.3s ease;
}
.secondaryButton:hover {
background-color: #007bff;
color: white;
}
.heroImage {
display: flex;
justify-content: center;
align-items: center;
}
.placeholder {
width: 300px;
height: 300px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
/* スキルセクション */
.skills {
padding: 4rem 0;
background-color: #f8f9fa;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.sectionTitle {
text-align: center;
font-size: 2rem;
margin: 0 0 3rem 0;
color: #333;
}
.skillsGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.skillCard {
background: white;
padding: 2rem;
border-radius: 12px;
text-align: center;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
.skillCard:hover {
transform: translateY(-5px);
}
.skillIcon {
font-size: 3rem;
color: #007bff;
margin-bottom: 1rem;
}
.skillCard h3 {
margin: 0 0 1rem 0;
color: #333;
}
.skillCard p {
margin: 0;
color: #666;
line-height: 1.6;
}
/* CTAセクション */
.cta {
padding: 4rem 0;
background-color: #333;
color: white;
text-align: center;
}
.cta h2 {
margin: 0 0 1rem 0;
font-size: 2rem;
}
.cta p {
margin: 0 0 2rem 0;
font-size: 1.1rem;
color: #ccc;
}
.ctaButton {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 1rem 2rem;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
transition: all 0.3s ease;
}
.ctaButton:hover {
background-color: #0056b3;
transform: translateY(-2px);
}
/* レスポンシブ対応 */
@media (max-width: 768px) {
.hero {
grid-template-columns: 1fr;
gap: 2rem;
padding: 2rem 0;
text-align: center;
}
.heroTitle {
font-size: 2rem;
}
.heroButtons {
justify-content: center;
}
.placeholder {
width: 200px;
height: 200px;
}
.skillsGrid {
grid-template-columns: 1fr;
}
}
このスタイルの特徴を説明しますね。
グリッドレイアウト
display: grid
を使って、コンテンツを整列させています。
レスポンシブ対応もしやすくて便利です。
ホバーエフェクト
ボタンやカードにマウスを乗せると、動きがあります。
transform: translateY(-5px)
で、少し浮き上がる効果を作っています。
グラデーション
プロフィール画像の部分に、きれいなグラデーションを使いました。
linear-gradient
で、複数の色を滑らかに混ぜています。
自己紹介ページを作ろう
次は、自分のことを詳しく紹介するページを作りましょう。
// src/pages/About/About.js
import React from 'react';
import { FaGraduationCap, FaBriefcase, FaAward } from 'react-icons/fa';
import styles from './About.module.css';
function About() {
const experiences = [
{
year: '2023',
title: 'フリーランスWebデベロッパーとして独立',
description: 'React.jsを中心としたフロントエンド開発に特化した活動を開始'
},
{
year: '2022',
title: 'IT企業でWebデベロッパーとして勤務',
description: 'チーム開発でのReact案件を担当し、実務経験を積む'
},
{
year: '2021',
title: 'プログラミング学習を開始',
description: 'HTML/CSS/JavaScriptの基礎から学習を開始し、Reactを習得'
}
];
const skills = [
{ name: 'React.js', level: 90 },
{ name: 'JavaScript', level: 85 },
{ name: 'HTML/CSS', level: 95 },
{ name: 'Node.js', level: 70 },
{ name: 'Git', level: 80 },
{ name: 'Figma', level: 75 }
];
return (
<div className={styles.about}>
{/* プロフィールセクション */}
<section className={styles.profile}>
<div className={styles.profileContent}>
<div className={styles.profileImage}>
<div className={styles.imagePlaceholder}>
<FaGraduationCap size={60} />
</div>
</div>
<div className={styles.profileText}>
<h1>田中太郎について</h1>
<p className={styles.intro}>
こんにちは!Webデベロッパーの田中太郎です。
React.jsを中心としたモダンなフロントエンド技術で、
ユーザーにとって使いやすく、美しいWebサイトとアプリケーションを制作しています。
</p>
<p>
プログラミングを始めて3年、常に新しい技術に興味を持ち、
学習を続けながら成長していくことを心がけています。
お客様のビジョンを技術で実現することにやりがいを感じています。
</p>
</div>
</div>
</section>
{/* 経歴セクション */}
<section className={styles.experience}>
<div className={styles.container}>
<h2 className={styles.sectionTitle}>
<FaBriefcase />
経歴
</h2>
<div className={styles.timeline}>
{experiences.map((exp, index) => (
<div key={index} className={styles.timelineItem}>
<div className={styles.timelineYear}>{exp.year}</div>
<div className={styles.timelineContent}>
<h3>{exp.title}</h3>
<p>{exp.description}</p>
</div>
</div>
))}
</div>
</div>
</section>
{/* スキルセクション */}
<section className={styles.skillsSection}>
<div className={styles.container}>
<h2 className={styles.sectionTitle}>
<FaAward />
スキル
</h2>
<div className={styles.skillsGrid}>
{skills.map((skill, index) => (
<div key={index} className={styles.skillItem}>
<div className={styles.skillHeader}>
<span className={styles.skillName}>{skill.name}</span>
<span className={styles.skillLevel}>{skill.level}%</span>
</div>
<div className={styles.skillBar}>
<div
className={styles.skillProgress}
style={{ width: `${skill.level}%` }}
></div>
</div>
</div>
))}
</div>
</div>
</section>
{/* 趣味・興味セクション */}
<section className={styles.interests}>
<div className={styles.container}>
<h2 className={styles.sectionTitle}>趣味・興味</h2>
<div className={styles.interestsGrid}>
<div className={styles.interestCard}>
<h3>🎵 音楽</h3>
<p>ジャズとクラシック音楽を聴くのが好きです。集中したいときのBGMとしても活用しています。</p>
</div>
<div className={styles.interestCard}>
<h3>📚 読書</h3>
<p>技術書はもちろん、ビジネス書や小説も読みます。新しい知識と視点を得ることを大切にしています。</p>
</div>
<div className={styles.interestCard}>
<h3>🏃♂️ ランニング</h3>
<p>健康維持とリフレッシュのために週3回ランニングをしています。頭をクリアにする大切な時間です。</p>
</div>
<div className={styles.interestCard}>
<h3>🌱 新技術学習</h3>
<p>新しいフレームワークやツールの学習が趣味です。技術の進歩についていくことを楽しんでいます。</p>
</div>
</div>
</div>
</section>
</div>
);
}
export default About;
自己紹介ページには、こんなセクションを作りました。
プロフィールセクション 基本的な自己紹介文を書いています。 読む人に親しみを感じてもらえるような文章にしました。
経歴セクション タイムライン形式で、これまでの経歴を表示します。 時系列で見ることができて、成長の過程がわかりやすいですね。
スキルセクション 技術スキルをプログレスバーで表示します。 視覚的にスキルレベルがわかるようにしました。
趣味・興味セクション 技術以外の興味も紹介します。 人となりを知ってもらうために大切な部分です。
このページのスタイルは、特にタイムラインとプログレスバーが特徴的です。
/* src/pages/About/About.module.css */
.about {
width: 100%;
}
/* プロフィールセクション */
.profile {
padding: 2rem 0 4rem;
}
.profileContent {
display: grid;
grid-template-columns: 300px 1fr;
gap: 3rem;
align-items: center;
}
.profileImage {
display: flex;
justify-content: center;
}
.imagePlaceholder {
width: 250px;
height: 250px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.profileText h1 {
font-size: 2.5rem;
margin: 0 0 1.5rem 0;
color: #333;
}
.intro {
font-size: 1.2rem;
line-height: 1.8;
color: #555;
margin-bottom: 1.5rem;
font-weight: 500;
}
.profileText p {
line-height: 1.8;
color: #666;
margin-bottom: 1rem;
}
/* 共通コンテナ */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.sectionTitle {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 2rem;
margin: 0 0 3rem 0;
color: #333;
}
/* 経歴セクション */
.experience {
padding: 4rem 0;
background-color: #f8f9fa;
}
.timeline {
position: relative;
padding-left: 2rem;
}
.timeline::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 2px;
background-color: #007bff;
}
.timelineItem {
position: relative;
margin-bottom: 3rem;
padding-left: 2rem;
}
.timelineItem::before {
content: '';
position: absolute;
left: -0.5rem;
top: 0.5rem;
width: 1rem;
height: 1rem;
background-color: #007bff;
border-radius: 50%;
}
.timelineYear {
font-size: 1.2rem;
font-weight: bold;
color: #007bff;
margin-bottom: 0.5rem;
}
.timelineContent h3 {
margin: 0 0 0.5rem 0;
color: #333;
font-size: 1.3rem;
}
.timelineContent p {
margin: 0;
color: #666;
line-height: 1.6;
}
/* スキルセクション */
.skillsSection {
padding: 4rem 0;
}
.skillsGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.skillItem {
margin-bottom: 1.5rem;
}
.skillHeader {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.skillName {
font-weight: 600;
color: #333;
}
.skillLevel {
font-size: 0.9rem;
color: #666;
}
.skillBar {
height: 8px;
background-color: #e9ecef;
border-radius: 4px;
overflow: hidden;
}
.skillProgress {
height: 100%;
background: linear-gradient(90deg, #007bff, #0056b3);
border-radius: 4px;
transition: width 0.8s ease;
}
/* 趣味・興味セクション */
.interests {
padding: 4rem 0;
background-color: #f8f9fa;
}
.interestsGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
}
.interestCard {
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
.interestCard:hover {
transform: translateY(-5px);
}
.interestCard h3 {
margin: 0 0 1rem 0;
color: #333;
font-size: 1.2rem;
}
.interestCard p {
margin: 0;
color: #666;
line-height: 1.6;
}
/* レスポンシブ対応 */
@media (max-width: 768px) {
.profileContent {
grid-template-columns: 1fr;
gap: 2rem;
text-align: center;
}
.imagePlaceholder {
width: 200px;
height: 200px;
}
.profileText h1 {
font-size: 2rem;
}
.timeline {
padding-left: 1rem;
}
.timelineItem {
padding-left: 1.5rem;
}
.skillsGrid {
grid-template-columns: 1fr;
}
.interestsGrid {
grid-template-columns: 1fr;
}
}
タイムライン
::before
疑似要素を使って、縦線と丸いポイントを作っています。
CSSだけで、きれいなタイムラインができますね。
プログレスバー
width
をパーセンテージで指定して、スキルレベルを表現しています。
transition
でアニメーションも付けました。
これで、自己紹介ページも完成です。
ルーティング:ページを繋げよう
複数のページができたので、React Routerでページ間の移動を設定しましょう。
App.jsを更新しよう
メインのApp.jsファイルを、React Router用に更新します。
// src/App.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Layout from './components/Layout/Layout';
import Home from './pages/Home/Home';
import About from './pages/About/About';
import Works from './pages/Works/Works';
import Contact from './pages/Contact/Contact';
import NotFound from './pages/NotFound/NotFound';
import './styles/global.css';
function App() {
return (
<Router>
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/works" element={<Works />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Layout>
</Router>
);
}
export default App;
ここで重要なのは、Router
で全体を囲んでいることです。
これで、アプリ全体でページ移動ができるようになります。
各Routeの説明
path="/"
:ホームページpath="/about"
:自己紹介ページpath="/works"
:作品一覧ページpath="/contact"
:お問い合わせページpath="*"
:存在しないURLへアクセスした時の404ページ
作品一覧ページを作ろう
作品を紹介するページを作ってみましょう。
// src/pages/Works/Works.js
import React, { useState } from 'react';
import { FaGithub, FaExternalLinkAlt, FaFilter } from 'react-icons/fa';
import styles from './Works.module.css';
function Works() {
const [selectedCategory, setSelectedCategory] = useState('all');
const works = [
{
id: 1,
title: 'Eコマースサイト',
description: 'React.jsとFirebaseを使用したモダンなオンラインショップ。リアルタイムな在庫管理と決済機能を実装。',
image: '/api/placeholder/400/250',
category: 'web',
technologies: ['React', 'Firebase', 'Stripe', 'CSS Modules'],
githubUrl: 'https://github.com',
liveUrl: 'https://example.com'
},
{
id: 2,
title: 'タスク管理アプリ',
description: 'チーム向けのタスク管理ツール。ドラッグ&ドロップでのタスク移動とリアルタイム同期機能。',
image: '/api/placeholder/400/250',
category: 'app',
technologies: ['React', 'Node.js', 'MongoDB', 'Socket.io'],
githubUrl: 'https://github.com',
liveUrl: 'https://example.com'
},
{
id: 3,
title: 'ポートフォリオサイト',
description: 'デザイナー向けのポートフォリオサイト。作品のカテゴリ分けとフィルタリング機能を実装。',
image: '/api/placeholder/400/250',
category: 'web',
technologies: ['React', 'CSS Grid', 'Framer Motion'],
githubUrl: 'https://github.com',
liveUrl: 'https://example.com'
},
{
id: 4,
title: '天気予報アプリ',
description: '位置情報を活用した天気予報アプリ。5日間の予報と詳細な気象データを表示。',
image: '/api/placeholder/400/250',
category: 'app',
technologies: ['React', 'OpenWeather API', 'Chart.js'],
githubUrl: 'https://github.com',
liveUrl: 'https://example.com'
}
];
const categories = [
{ value: 'all', label: 'すべて' },
{ value: 'web', label: 'Webサイト' },
{ value: 'app', label: 'アプリケーション' }
];
const filteredWorks = selectedCategory === 'all'
? works
: works.filter(work => work.category === selectedCategory);
return (
<div className={styles.works}>
<div className={styles.header}>
<h1>作品一覧</h1>
<p>これまでに制作したWebサイトとアプリケーションをご紹介します。</p>
</div>
{/* フィルター */}
<div className={styles.filter}>
<div className={styles.filterLabel}>
<FaFilter />
カテゴリー:
</div>
<div className={styles.filterButtons}>
{categories.map(category => (
<button
key={category.value}
className={`${styles.filterButton} ${
selectedCategory === category.value ? styles.active : ''
}`}
onClick={() => setSelectedCategory(category.value)}
>
{category.label}
</button>
))}
</div>
</div>
{/* 作品グリッド */}
<div className={styles.worksGrid}>
{filteredWorks.map(work => (
<div key={work.id} className={styles.workCard}>
<div className={styles.workImage}>
<img src={work.image} alt={work.title} />
<div className={styles.workOverlay}>
<div className={styles.workLinks}>
<a
href={work.githubUrl}
target="_blank"
rel="noopener noreferrer"
className={styles.workLink}
aria-label="GitHub"
>
<FaGithub />
</a>
<a
href={work.liveUrl}
target="_blank"
rel="noopener noreferrer"
className={styles.workLink}
aria-label="ライブデモ"
>
<FaExternalLinkAlt />
</a>
</div>
</div>
</div>
<div className={styles.workContent}>
<h3>{work.title}</h3>
<p>{work.description}</p>
<div className={styles.technologies}>
{work.technologies.map((tech, index) => (
<span key={index} className={styles.techTag}>
{tech}
</span>
))}
</div>
</div>
</div>
))}
</div>
{filteredWorks.length === 0 && (
<div className={styles.noWorks}>
<p>該当する作品がありません。</p>
</div>
)}
</div>
);
}
export default Works;
作品一覧ページには、こんな機能を入れました。
フィルター機能
カテゴリーごとに作品を絞り込めます。
useState
を使って、選択されたカテゴリーを管理しています。
作品カード 各作品を魅力的に見せるカードデザインにしました。 画像にマウスを乗せると、GitHubやデモサイトのリンクが表示されます。
技術タグ 使用した技術を小さなタグで表示します。 どんな技術で作ったかが一目でわかりますね。
お問い合わせページを作ろう
最後に、お問い合わせフォームのページを作りましょう。
// src/pages/Contact/Contact.js
import React, { useState } from 'react';
import { FaEnvelope, FaPhone, FaMapMarkerAlt, FaPaperPlane } from 'react-icons/fa';
import styles from './Contact.module.css';
function Contact() {
const [formData, setFormData] = useState({
name: '',
email: '',
subject: '',
message: ''
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitStatus, setSubmitStatus] = useState(null);
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
// 実際のアプリケーションでは、ここでAPIにデータを送信
try {
// 模擬的な送信処理
await new Promise(resolve => setTimeout(resolve, 2000));
setSubmitStatus('success');
setFormData({ name: '', email: '', subject: '', message: '' });
} catch (error) {
setSubmitStatus('error');
} finally {
setIsSubmitting(false);
}
};
return (
<div className={styles.contact}>
<div className={styles.header}>
<h1>お問い合わせ</h1>
<p>プロジェクトのご相談やお見積もりなど、お気軽にご連絡ください。</p>
</div>
<div className={styles.contactContent}>
{/* 連絡先情報 */}
<div className={styles.contactInfo}>
<h2>連絡先</h2>
<div className={styles.contactItem}>
<FaEnvelope className={styles.contactIcon} />
<div>
<h3>メールアドレス</h3>
<p>tanaka@example.com</p>
</div>
</div>
<div className={styles.contactItem}>
<FaPhone className={styles.contactIcon} />
<div>
<h3>電話番号</h3>
<p>090-1234-5678</p>
</div>
</div>
<div className={styles.contactItem}>
<FaMapMarkerAlt className={styles.contactIcon} />
<div>
<h3>所在地</h3>
<p>東京都渋谷区</p>
</div>
</div>
<div className={styles.responseTime}>
<h3>レスポンス時間</h3>
<p>
通常24時間以内にご返信いたします。<br />
お急ぎの場合は電話でご連絡ください。
</p>
</div>
</div>
{/* お問い合わせフォーム */}
<div className={styles.contactForm}>
<h2>お問い合わせフォーム</h2>
{submitStatus === 'success' && (
<div className={styles.successMessage}>
お問い合わせありがとうございます。24時間以内にご返信いたします。
</div>
)}
{submitStatus === 'error' && (
<div className={styles.errorMessage}>
送信に失敗しました。しばらくしてから再度お試しください。
</div>
)}
<form onSubmit={handleSubmit}>
<div className={styles.formGroup}>
<label htmlFor="name">お名前 *</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
className={styles.formInput}
/>
</div>
<div className={styles.formGroup}>
<label htmlFor="email">メールアドレス *</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
className={styles.formInput}
/>
</div>
<div className={styles.formGroup}>
<label htmlFor="subject">件名 *</label>
<input
type="text"
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
required
className={styles.formInput}
/>
</div>
<div className={styles.formGroup}>
<label htmlFor="message">メッセージ *</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
required
rows="6"
className={styles.formTextarea}
/>
</div>
<button
type="submit"
disabled={isSubmitting}
className={styles.submitButton}
>
{isSubmitting ? (
'送信中...'
) : (
<>
送信する
<FaPaperPlane />
</>
)}
</button>
</form>
</div>
</div>
</div>
);
}
export default Contact;
お問い合わせページには、こんな機能を盛り込みました。
連絡先情報 メールアドレスや電話番号などを見やすく表示します。 アイコンを使うことで、視覚的にわかりやすくしました。
お問い合わせフォーム
名前、メールアドレス、件名、メッセージを入力できます。
useState
でフォームの状態を管理しています。
送信状態の管理 送信中や送信完了のメッセージを表示します。 ユーザーにとって分かりやすいUIになっています。
404ページを作ろう
存在しないURLにアクセスしたときの404ページも作っておきましょう。
// src/pages/NotFound/NotFound.js
import React from 'react';
import { Link } from 'react-router-dom';
import { FaHome, FaExclamationTriangle } from 'react-icons/fa';
import styles from './NotFound.module.css';
function NotFound() {
return (
<div className={styles.notFound}>
<div className={styles.content}>
<div className={styles.icon}>
<FaExclamationTriangle />
</div>
<h1>404</h1>
<h2>ページが見つかりません</h2>
<p>
お探しのページは存在しないか、移動された可能性があります。<br />
URLをご確認いただくか、ホームページからお探しください。
</p>
<Link to="/" className={styles.homeButton}>
<FaHome />
ホームページへ戻る
</Link>
</div>
</div>
);
}
export default NotFound;
404ページは、ユーザーが迷子になったときの案内役です。 分かりやすいメッセージと、ホームページへのリンクを用意しています。
これで、全てのページが完成しました。
ビルドとデプロイ:サイトを世界に公開しよう
いよいよ、作ったサイトをインターネットに公開してみましょう。
本番用ビルドを作ろう
まず、サイトを本番環境用に最適化します。
# 本番用ビルドの作成
npm run build
# ビルド結果の確認
ls -la build/
# ローカルでビルド結果をプレビュー
npx serve -s build
npm run build
を実行すると、build
フォルダが作られます。
この中に、最適化されたファイルが入っています。
ファイルサイズが小さくなって、読み込みが早くなりますよ。
Vercelでデプロイしよう
Vercelは、Reactサイトを簡単に公開できるサービスです。
# Vercel CLIのインストール
npm install -g vercel
# Vercelにログイン
vercel login
# プロジェクトのデプロイ
vercel
# 本番環境にデプロイ
vercel --prod
初回デプロイ時は、いくつか質問されます。 基本的には、デフォルトの設定で大丈夫です。
デプロイが完了すると、URLが表示されます。 そのURLにアクセスすると、あなたのサイトが見えるはずです。
Netlifyでデプロイしよう
Netlifyも、人気のデプロイサービスです。
# Netlify CLIのインストール
npm install -g netlify-cli
# Netlifyにログイン
netlify login
# ビルドとデプロイ
netlify deploy --prod --dir=build
Netlifyの場合は、GitHubと連携することもできます。 コードをプッシュするたびに、自動でデプロイされるので便利ですね。
GitHub Pagesでデプロイしよう
GitHubを使っている場合は、GitHub Pagesも選択肢の一つです。
# gh-pagesパッケージのインストール
npm install -D gh-pages
# package.jsonにhomepage設定を追加
{
"homepage": "https://yourusername.github.io/your-repo-name"
}
# package.jsonにデプロイスクリプトを追加
{
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
}
}
# デプロイの実行
npm run deploy
設定ファイルを更新してから、npm run deploy
を実行します。
しばらくすると、GitHubのURLでサイトが見られるようになります。
どのサービスを選んでも、基本的な流れは同じです。 使いやすそうなものを選んでみてくださいね。
パフォーマンス最適化:サイトを高速化しよう
サイトが完成したら、さらに快適に使えるよう最適化してみましょう。
画像の最適化をしよう
画像の読み込みを改善するコンポーネントを作ってみます。
// src/components/OptimizedImage/OptimizedImage.js
import React, { useState } from 'react';
import styles from './OptimizedImage.module.css';
function OptimizedImage({ src, alt, className, ...props }) {
const [isLoaded, setIsLoaded] = useState(false);
const [hasError, setHasError] = useState(false);
const handleLoad = () => {
setIsLoaded(true);
};
const handleError = () => {
setHasError(true);
};
return (
<div className={`${styles.imageContainer} ${className}`}>
{!isLoaded && !hasError && (
<div className={styles.placeholder}>
読み込み中...
</div>
)}
{hasError ? (
<div className={styles.errorPlaceholder}>
画像を読み込めませんでした
</div>
) : (
<img
src={src}
alt={alt}
onLoad={handleLoad}
onError={handleError}
className={`${styles.image} ${isLoaded ? styles.loaded : ''}`}
loading="lazy"
{...props}
/>
)}
</div>
);
}
export default OptimizedImage;
この画像コンポーネントには、こんな機能があります。
遅延読み込み
loading="lazy"
で、スクロールしたときに画像を読み込みます。
最初の読み込み時間を短縮できますね。
読み込み状態の表示 画像が読み込まれる前は、プレースホルダーを表示します。 ユーザーにとって分かりやすいUIになります。
エラーハンドリング 画像の読み込みに失敗したときも、適切にメッセージを表示します。
コードスプリッティングをしよう
ページごとにコードを分割して、初期読み込みを早くしましょう。
// src/App.js
import React, { Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Layout from './components/Layout/Layout';
import LoadingSpinner from './components/LoadingSpinner/LoadingSpinner';
// 遅延読み込みでコンポーネントをインポート
const Home = React.lazy(() => import('./pages/Home/Home'));
const About = React.lazy(() => import('./pages/About/About'));
const Works = React.lazy(() => import('./pages/Works/Works'));
const Contact = React.lazy(() => import('./pages/Contact/Contact'));
const NotFound = React.lazy(() => import('./pages/NotFound/NotFound'));
function App() {
return (
<Router>
<Layout>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/works" element={<Works />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</Layout>
</Router>
);
}
export default App;
React.lazy()
を使うことで、ページが必要になったときだけコードを読み込みます。
これを「コードスプリッティング」と呼びます。
最初のページ表示が早くなって、ユーザー体験が向上しますよ。
SEO対策をしよう
検索エンジンに見つけてもらいやすくするため、SEO対策も入れましょう。
// src/components/SEO/SEO.js
import React from 'react';
import { Helmet } from 'react-helmet-async';
function SEO({
title = 'My Portfolio',
description = 'React.jsを使ったWebサイト制作を行うデベロッパーのポートフォリオサイト',
keywords = 'React, JavaScript, Web開発, フロントエンド',
image = '/og-image.jpg',
url = window.location.href
}) {
const siteTitle = title === 'My Portfolio' ? title : `${title} | My Portfolio`;
return (
<Helmet>
<title>{siteTitle}</title>
<meta name="description" content={description} />
<meta name="keywords" content={keywords} />
{/* Open Graph */}
<meta property="og:title" content={siteTitle} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<meta property="og:url" content={url} />
<meta property="og:type" content="website" />
{/* Twitter Card */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={siteTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={image} />
{/* その他のメタタグ */}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href={url} />
</Helmet>
);
}
export default SEO;
このSEOコンポーネントを各ページで使うことで、検索エンジン対策ができます。
重要なメタタグ
title
:ページのタイトルdescription
:ページの説明文og:image
:SNSでシェアしたときの画像canonical
:正規のURLを指定
これらを適切に設定することで、Googleなどの検索結果に表示されやすくなります。
まとめ
お疲れさまでした! ReactでWebサイトを作る方法を、一から学んできましたね。
今回学んだこと
環境構築 Node.jsとCreate React Appでプロジェクトを作成しました。 面倒な設定は自動化されているので、すぐに開発を始められましたね。
コンポーネント設計 ヘッダー、フッター、各ページなど、機能ごとにコンポーネントを分けました。 再利用しやすく、メンテナンスしやすい構造になっています。
ルーティング React Routerで複数ページのサイトを作りました。 SPAでも、普通のWebサイトのように動作しますね。
スタイリング CSS Modulesで見た目を整えました。 レスポンシブデザインで、スマホでも見やすくなっています。
デプロイ Vercel、Netlify、GitHub Pagesでサイトを公開しました。 コマンド一つで、世界中の人に見てもらえるようになります。
最適化 パフォーマンス向上とSEO対策も学びました。 ユーザーにとって快適で、検索エンジンにも優しいサイトになりました。
今後の発展
今回作ったポートフォリオサイトは、あくまでスタートラインです。 ここから、さらに色々な機能を追加できますよ。
追加できる機能
- アニメーション効果(Framer Motion)
- CMSとの連携(Strapi、Contentful)
- PWA化(オフラインでも動作)
- ダークモード切り替え
- 多言語対応(i18n)
技術は日々進歩しています。 新しいライブラリやフレームワークも次々と登場しますね。
でも、今回学んだ基礎があれば、どんな新技術にも対応できるはずです。
最後に
Reactでのサイト作りは、最初は難しく感じるかもしれません。 でも、一つずつ積み重ねていけば、必ずできるようになります。
今回の記事を参考に、ぜひあなただけのオリジナルサイトを作ってみてください。 きっと、楽しい発見がたくさんあるはずです。
作ったサイトは、ポートフォリオとしても活用できます。 就職活動や営業活動でも、きっと役に立ちますよ。
頑張って、素敵なReactサイトを作ってくださいね!