【1日で再現可能】ReactとGatsbyでポートフォリオを作った
10分目次
僕の場合はコーディングしたので 1 週間弱かかりましたが、そこまでリソースを割きたくない場合はgit clone
すればほぼ一瞬でポートフォリオが作れます。早ければ 1 時間もかからないかと。その秘密が Gatsby です。
[超簡単]Gatsby.js でカッコいいポートフォリオを作成する方法
詳しい作成手順は上記記事に掲載されているのでここでは割愛します。どんなものが作れるかといった事例が少ないのでここでは「こんな感じのページをこんなふうに作ったよ」という情報を提供します。
ですが、そのまえに Gatsby のいいところをまとめてみます。
Gatsby(と Netlify)を使うと幸せになれる理由 5 つ
- ソースコードを GitHub で管理できる(=管理やバックアップが楽だし、ウィルス感染もしない)
- プラグインシステムがあり、2000 以上のプラグインが公開されている。WordPress ライクな構築が可能
- テーマシステムもあり、好きなテーマを clone すれば簡単に構築可能(当サイトもdelogというテーマをベースにして作りました)
- サーバー代が 0 円(ランニングコストはドメイン代のみ。独自ドメイン使わなければ永年無料)
- サーバー代 0 円なのに高速サーバーよりも数倍高速
とにかく特筆すべきが超高速なサイトを誰でも作れると言う点です。僕のサイトではヘッダー下にメニューを置いてるので試しにそのメニューをクリックしてサイト間を移動してみてください。以前、WordPress テーマ『Godios』が一斉を風靡し、衝撃を受けましたがそれ以上の衝撃的速さです。もう conoha wing、XSERVER、ロリポップハイスピードなどを高いお金を払って契約する必要もないのです。また、すでにいくつかの記事で検索上位を占めており、SEO 面でも問題ありません。
作ったポートフォリオ
作ったポートフォリオのページはこちらです。このページのコードは以下の通り。
import React from "react";
import { graphql } from "gatsby";
import Helmet from "react-helmet";
// COMPONENT
import Seo from "../components/Seo";
import Header from "../components/Header";
import FirstView from "../components/Portfolio/FirstView";
import Works from "../components/Portfolio/Works";
import Skills from "../components/Portfolio/Skills";
import PortfolioForm from "../components/Portfolio/PortfolioForm";
import Footer from "../components/Footer";
// CSS
import GlobalStyle from "../styles/global";
const Portfolio = ({ data, location }) => {
const siteUrl = data.site.siteMetadata.siteUrl;
const works = data.allWork;
const skills = data.allSkill;
return (
<>
<GlobalStyle />
<Seo title="9631kunn's portfolio" keywords={["portfolio"]} />
<Helmet>
<link rel="canonical" href={`${siteUrl}/portfolio`} />
</Helmet>
<Header location={location} />
<FirstView />
<Works {...works} />
<Skills {...skills} />
<PortfolioForm />
<Footer />
</>
);
};
export default Portfolio;
export const portfolioQuery = graphql`
query portfolioQuery {
site {
siteMetadata {
title
siteUrl
description
}
}
allWork {
nodes {
name
date
lang
description
more
url
}
}
allSkill {
nodes {
title
level
}
}
}
`;
必要なデータは GraphQL 経由で取得し、それを各コンポーネントに渡しています。共通スタイルは<GlobalStyle />
タグで出力。ポートフォリオなので SEO はどうでもいいですが一応。
ファーストビュー
ファーストビューでは isometric なアイコンがお出迎えしてくれます。この手のデザインがすごく好きなんですよね。画像の取得は
このあたりに頼っています。オススメです。
ファーストビューのコード
import React from "react";
// CSS
import styled from "styled-components";
import "typeface-quicksand";
// IMAGE
import icon from "../../icon/categoryIcon/pinata.svg";
const FirstView = () => {
const Wrap = styled.section`
background: #e1e8ed;
border-radius: 0 0 50% 50% / 0 0 20% 20%;
height: 500px;
padding-top: 60px;
position: relative;
z-index: 0;
`;
const Inner = styled.div`
align-items: center;
display: grid;
height: 100%;
margin: auto;
max-width: 768px;
padding: 80px 0;
grid-template-columns: 1fr 100px;
width: 95%;
`;
const Block = styled.div`
h1 {
font-family: "Quicksand", sans-serif;
font-size: 28px;
@media (min-width: 480px) {
font-size: 38px;
}
@media (min-width: 769px) {
font-size: 45px;
}
}
picture {
position: relative;
&::after {
background: #b3bfc7;
border-radius: 50%;
bottom: 0;
content: "";
height: 40px;
left: 5px;
position: absolute;
width: 100%;
z-index: -1;
}
}
`;
return (
<Wrap>
<Inner>
<Block>
<h1>Welcome To My Portfolio</h1>
</Block>
<Block>
<picture>
<img src={icon} alt="アイコン" />
</picture>
</Block>
</Inner>
</Wrap>
);
};
export default FirstView;
picture
タグはimg
に style 適用させるのがあんまり好きでないのでとりあえず親として使っているだけで、div
でもなんでもよかったのですが。アイコンの影部分はpicture
の擬似要素で作りました。Wrap
の上部に余白とっているのはヘッダーがfixed
なので。
作ったモノ一覧
ここら辺のデータは先述した通り、GraphQL から取得しています。配列で受け取ったものを map で展開してコンポーネントに一個ずつ渡していっています。スマホではややレイアウトが異なり、こんな感じに表示されます。
さすがにこれだけの変化をひとつのコードから CSS だけで変更すると言うのは困難だったため、PC ではpc__none
クラスなど用いて pc では表示しないようにしました。クラスによる非表示でなく、window サイズによる出力の切り替えもできますが、面倒だった今回は迅速に終えるために CSS で制御しています。
作ったモノ一覧のコード
import React from "react";
// COMPONENT
import Title from "./Title";
import Work from "./Work";
import Timeline from "./Timeline";
// CSS
import styled from "styled-components";
const Works = (props) => {
const Wrap = styled.section`
padding-top: 200px;
`;
const Flex = styled.div`
display: flex;
margin: auto;
max-width: 1024px;
width: 100%;
> * {
width: 100%;
@media (min-width: 769px) {
margin: 0 auto;
width: calc(50% - 20px);
}
}
`;
const TimeLineWrap = styled.div`
height: 100%;
padding-right: 50px;
position: sticky;
top: 60px;
@media (max-width: 768px) {
display: none;
}
`;
const WorkWrap = styled.div``;
return (
<Wrap>
<Title title="All Works." />
<Flex id="worksArea">
<TimeLineWrap>
{props.nodes.map((work, i) => (
<Timeline {...work} key={work.name} index={i} />
))}
</TimeLineWrap>
<WorkWrap>
{props.nodes.map((work, i) => (
<Work {...work} key={work.name} index={i} />
))}
</WorkWrap>
</Flex>
</Wrap>
);
};
export default Works;
今気付いたのですが、index は渡す必要ありませんね。当初はタイトル前に数字をつけていたのでその名残です。
スキル一覧
波線による区切りは SVG を使用しています。Make some waves!というサイトで簡単に generate できます。ちなみにMake some waves!も Gatsby 製です。
スキル一覧のコード
import React from "react";
// CSS
import styled from "styled-components";
import "typeface-quicksand";
// COMPONENT
import Skill from "./Skill";
import Title from "./Title";
const Skills = (props) => {
const Wrap = styled.section`
background: #e1e8ed;
margin-bottom: -10px;
margin-top: -10px;
padding-top: 60px;
`;
const Grid = styled.div`
display: grid;
gap: 30px;
grid-template-columns: 1fr 1fr;
@media (min-width: 480px) {
grid-template-columns: 1fr 1fr 1fr;
}
@media (min-width: 769px) {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
margin: auto;
max-width: 768px;
`;
return (
<>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
<path
fill="#e1e8ed"
fillOpacity="1"
d="M0,224L34.3,197.3C68.6,171,137,117,206,112C274.3,107,343,149,411,192C480,235,549,277,617,256C685.7,235,754,149,823,144C891.4,139,960,213,1029,234.7C1097.1,256,1166,224,1234,202.7C1302.9,181,1371,171,1406,165.3L1440,160L1440,320L1405.7,320C1371.4,320,1303,320,1234,320C1165.7,320,1097,320,1029,320C960,320,891,320,823,320C754.3,320,686,320,617,320C548.6,320,480,320,411,320C342.9,320,274,320,206,320C137.1,320,69,320,34,320L0,320Z"
></path>
</svg>
<Wrap>
<Title title="Skills" />
<Grid>
{props.nodes.map((skill) => {
return <Skill {...skill} key={skill.title} />;
})}
</Grid>
</Wrap>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
<path
fill="#e1e8ed"
fillOpacity="1"
d="M0,288L34.3,261.3C68.6,235,137,181,206,170.7C274.3,160,343,192,411,202.7C480,213,549,203,617,197.3C685.7,192,754,192,823,170.7C891.4,149,960,107,1029,90.7C1097.1,75,1166,85,1234,112C1302.9,139,1371,181,1406,202.7L1440,224L1440,0L1405.7,0C1371.4,0,1303,0,1234,0C1165.7,0,1097,0,1029,0C960,0,891,0,823,0C754.3,0,686,0,617,0C548.6,0,480,0,411,0C342.9,0,274,0,206,0C137.1,0,69,0,34,0L0,0Z"
></path>
</svg>
</>
);
};
export default Skills;
スキルも GraphQL から配列で取得できるので map で展開。Skill コンポーネントに渡しています。
問い合わせ(&フッター)
問い合わせフォームは Netlify フォームを使用しました。Gatsby 製ブログでの Netlify フォームの導入手順は別途記事にしています。
Netlify Form を Gatsby ブログに導入する手順
問い合わせフォーム部分のコード
import React from "react";
// STYLE
import Title from "./Title";
import styled from "styled-components";
import blockWrapperStyle from "../../styles/blockWrapper";
// COMPONENT
import Form from "../Form";
const Wrap = styled.section`
${blockWrapperStyle}
`;
const PortfolioForm = () => {
return (
<Wrap>
<Title title="Get in touch" />
<Form />
</Wrap>
);
};
export default PortfolioForm;
Form コンポーネントもこちらの記事でコード公開しています。
まとめ
Gatsbyは大流行(⭐️45.8K)ですが、まだまだ見る機会が少ない気がします。もちろんエンジニア以外にとってはなかなか難しいところではあるのですが、少なくともエンジニアにとっては WordPress に代わる大きな選択肢になり得るかと。この機会に是非とも触ってみていただければ幸いです:)
P.S.
gatsby-starter-portfolio-caraとかもめっちゃかっこいいし、僕が Gatsby に興味持ったテーマでもあります。デモはこちら。オススメです。