SEO
システム
javascript
next.js
2022.03.18
react/Next.jsでAPIから値を取得して表示する使い方
こんにちは。月岡です。
今日はNext.jsでAPIから値をとる使い方を書こうかな思います。
まず何よりも先にnodenvの設定をします。
あるフォルダに入った時に自動で必要なnode.jsのバージョンに切り替えてくれるバージョン管理ツールです。
git clone https://github.com/nodenv/nodenv.git ~/.nodenv
cd ~/.nodenv && src/configure && make -C src
echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(nodenv init -)"' >> ~/.zshrc
exec $SHELL -l
nodenv --version
mkdir -p "$(nodenv root)"/plugins
git clone https://github.com/nodenv/node-build.git "$(nodenv root)"/plugins/node-build
git clone https://github.com/nodenv/nodenv-update.git "$(nodenv root)"/plugins/nodenv-update
nodenv update
nodenv install --list
nodenv --version
nodenv install 12.16.3 #自分のPCにバージョンを入れる
nodenv global 12.16.3 #PC全体のバージョンの指定
cd /XXXXX #特定のフォルダに移動
nodenv local 12.16.3 #特定のフォルダに入ったときだけこのバージョンに自動で変更
cat .node-version #このフォルダに入ったときだけ実行されるバージョンの確認
これで、プロジェクトフォルダのnodeのバージョンを決めたとして、本題にいってみます。
Next.jsの環境構築をやってみます。
nextjsはSSRとかもやってくれて便利です^^b
では早速、(nodeとかは入ってる前提で。)
npm init -y
npm install --save react react-dom next
mkdir pages
vim package.json
で、これに差し替えてください。
{
"name": "self_project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"next": "^9.4.4",
"react": "^16.13.1",
"react-dom": "^16.13.1"
}
}
で、保存をします。
そして、
npm run dev
これで http://localhost:3000 にアクセスすると見事に404のページがw
どうやら成功したようだな…
では、pagesのフォルダに入りまして、
そこでindex.jsを作ってみましょう。
それで404はなくなります。
デフォルトでそこを見るようになってるからです。
ちなみに、
export default () => (
<>
<h1>トップなりよ。</h1>
本文
</>
);
これでも動くけど、
だいたいは、
const Index = () => (
<>
<h1>トップなりよ。</h1>
本文
</>
);
export default Index;
こうしますよ。
ちなみに、頭に、
import Link from ‘next/Link’;
を書くと、リンクのための記述を使えるようになりますよ。
例えば、index.jsと同じpagesの階層に、blog.jsとかを作って、
import Link from 'next/Link';
const Blog = () => (
<>
<h1>ブログなりよ。</h1>
本文
<Link href="index.js"><a>TOPへリンク</a></Link>
</>
);
export default Blog;
で、index.jsは
import Link from 'next/Link';
const Index = () => (
<>
<h1>TOPなりよ。</h1>
本文
<Link href="blog.js"><a>ブログへリンク</a></Link>
</>
);
export default Index;
とかにすると、リンクがつながるのがわかると思います。
使い方わかりやすいですね。
Layout.jsでレイアウトを組んでみるか…。
次に、pagesフォルダと同階層にcomponentsと言うフォルダを作り
その中に、Layout.jsを作ってみましょう。
const Layout = (props) => (
<>
{props.children}
</>
);
export default Layout;
これだけだと価値がわからないので、
メニューを追加しようと思います。
このファイルはいったん保存してください。
そして同階層(つまりcomponentsフォルダの中)にNavi.jsを作りましょう。
そして、そのNavi.jsをこのようにします。
import Link from 'next/link';
const Navi = () => (
<div>
<ul>
<li><Link href="/"><a>トップ</a></Link></li>
<li><Link href="/blog"><a>ブログ</a></Link></li>
</ul>
</div>
);
export default Navi;
これを先ほどのLayout.jsから呼び出すんですが、
ちょっとcssもこのメニューに付け足したいのでcssを書きます。
cssをそのまま書くときは<style jsx>{`XXXXXX`}</style>この中に書くので、
import Link from 'next/link';
const Navi = () => (
<div>
<ul>
<li><Link href="/"><a>トップ</a></Link></li>
<li><Link href="/blog"><a>ブログ</a></Link></li>
</ul>
<style jsx>{`
ul {
background: #333;
color: #fff;
list-style:none;
display:flex;
}
ul li {
font-size:18px;
margin-right:20px;
}
ul li a {
color: #FFF;
text-decoration:none;
}
`}</style>
</div>
);
export default Navi;
こんな感じで書くことができます。
では、このNavi.jsをLayout.jsで呼び出してみます。
import Navi from './Navi';
const Layout = (props) => (
<>
<Navi/>
{props.children}
</>
);
export default Layout;
このように、初めにNaviを読み込んで、
Naviを表示したいところに<Navi/>を書くだけで終わりです。
使い方の例としては、このような感じで、たくさんコンポートネントを作って埋め込むことができます。
Layout.jsは
<header/>
<content/>
<sidebar/>
<footer/>
とかになっていくイメージですね。
<head>を書いて、cssを読み込んだりする場合やtitleタグを書く場合はどうするのか
それは、next/headを読み込みます。
layout.jsで読み込んでみましょう。
import Head from 'next/head';
import Navi from './Navi';
const Layout = (props) => (
<>
<Head>
<title>タイトルくるんごね</title>
<link rel="stylesheet" href="https://bootswatch.com/4/cerulean/bootstrap.min.css" />
</Head>
<Navi/>
{props.children}
</>
);
export default Layout;
こんな感じになります。
では、index.jsやblog.jsにこのレイアウトを適応させていきます。
まずindex.js
import Link from 'next/Link';
import Layout from '../components/Layout';
const Index = () => (
<Layout>
<>
<h1>トップなりよ。</h1>
本文
</>
</Layout>
);
export default Index;
このように、Layout.jsを読み込んで、<Layout>タグで囲ってあげると
その中身が、Layout.jsの{props}に入ると言う考え方です。
blog.jsも同じように
import Link from 'next/Link';
import Layout from '../components/Layout';
const Blog = () => (
<Layout>
<>
<h1>ブログなりよ。</h1>
本文
</>
</Layout>
);
export default Blog;
と書きます。
これで、メニューができて、ブログと行き来するようなページができました。
次にapi通信してみます。
わりと参考ページがレイアウトまでのところが多くて、
どうやってapi通信して表示するのかまで書いていなかったりするので、
今回はapiを通信をして値を取得し、表示すると言うところまでやってみます。
まず、ターミナルで、
npm install isomorphic-unfetch
を実行しましょう。
その後にpackage.jsonをみると…
{
"name": "self_project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@zeit/next-css": "^0.1.5",
"isomorphic-unfetch": "^3.0.0",
"next": "^9.4.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-redux": "^5.0.7",
"react-scripts": "1.1.4",
"redux": "^4.0.0",
"redux-devtools": "^3.4.1"
}
}
“isomorphic-unfetch”: “^3.0.0”,が追加されてますね。
そしたらpagesフォルダのindex.jsを編集しましょう。
import Fetch from 'isomorphic-unfetch';
import Layout from '../components/Layout';
const Index = () => (
<Layout>
<>
<h1>トップなりよ。</h1>
本文
</>
</Layout>
);
Index.getInitialProps = async function(){
const res = await fetch('https://api.coindesk.com/v1/bpi/currentprice.json');
const data = await res.json();
return {
bpi: data
}
}
export default Index;
上の方で、非同期通信するためにisomorphic-unfetchをインポートをしています。
asyncやawaitは非同期するときに昔から出てくるjavascript表現なので直感的にもわかりやすいです。
apiをコールしてresに入れて、json型にしたものをdataに入れると
{“time”:{“updated”:”Sep 18, 2013 17:27:00 UTC“,”updatedISO”:”2013-09-18T17:27:00+00:00″},”disclaimer”:”This data was produced from the CoinDesk Bitcoin Price Index. Non-USD currency data converted using hourly conversion rate from openexchangerates.org”,”bpi”:{“USD”:{“code”:”USD”,”symbol”:”$”,”rate”:”126.5235″,”description”:”United States Dollar”,”rate_float”:126.5235},”GBP”:{“code”:”GBP”,”symbol”:”£”,”rate”:”79.2495″,”description”:”British Pound Sterling”,”rate_float”:79.2495},”EUR”:{“code”:”EUR”,”symbol”:”€”,”rate”:”94.7398″,”description”:”Euro”,”rate_float”:94.7398}}}
こんな感じで入ってるので、
bpi: dataとすることでdataのなかのbpiオブジェクトを
bpiに入れています。
非同期で取得したオブジェクトを表示する
index.jsの引数にpropsを入れます。
非同期でreturnされたオブジェクトはpropsに入ってくるので、
上のオブジェクトの太くなってる部分を表示してみましょう。
bpiのなかのtimeのなかのupdatedを指定すれば、Sep 18, 2013 17:27:00 UTCと表示されるはずです。
import Fetch from 'isomorphic-unfetch';
import Layout from '../components/Layout';
const Index = (props) => (
<Layout>
<>
<h1>トップなりよ。</h1>
{props.bpi.time.updated}
</>
</Layout>
);
Index.getInitialProps = async function(){
const res = await fetch
('https://api.coindesk.com/v1/bpi/currentprice.json');
const data = await res.json();
return {
bpi: data
}
}
export default Index;
Pricesクラスから現在のUSDの値段を表示
次に、componentsフォルダの中にPrices.jsを作り、
その中に、
このコードを書いてください。
class Prices extends React.Component {
state = {
currency: 'USD'
}
render(){
return(
<div>
<ul className="list-group">
<li className="list-group-item">
bitcoin rate for {this.props.okane.USD.description}
:<span className='badge badge-primary'>{this.props.okane.USD.code}</span>
<strong>{this.props.okane.USD.rate}</strong>
</li>
</ul>
</div>
);
}
}
export default Prices;
クラスの書き方と、renderするとコンポーネントとしてindex.jsに<Prices />で表示できるようにする文法がわかればいいかなと。
気になるのが、
this.props.okane ですよね。
これはindex.jsにこのように書くことで、
使えるようになります。
import Fetch from 'isomorphic-unfetch';
import Layout from '../components/Layout';
import Prices from '../components/Prices';
const Index = (props) => (
<Layout>
<>
<h1>トップなりよ。</h1>
{props.bpi.time.updated}
<Prices okane={props.bpi.bpi}/>
</>
</Layout>
);
Index.getInitialProps = async function(){
const res = await fetch
('https://api.coindesk.com/v1/bpi/currentprice.json');
const data = await res.json();
return {
bpi: data
};
}
export default Index;
つまり、apiのデータをbpiというオブジェクトに入れましたが、
その中にさらにbpiという同名のオブジェクトがたまたまあり、その中にUSDとかのパラメータがあり、そこに現在のUSDの金額とかがあったので、
bpi.bpiのオブジェクトをokaneという名前で、Priceクラスに引き渡してると考えてください。
ちなみに、今、画面ではこんな風に見えています。
選択したらリアルタイムに金額が出るように変更…するか..。
Price.jsをこのように変えてみてください。
class Prices extends React.Component {
state = {
currency: 'USD'
}
render(){
let list = '';
if (this.state.currency === 'USD') {
list = <li className="list-group-item">
bitcoin rate for {this.props.okane.USD.description}
:<span className='badge badge-primary'>{this.props.okane.USD.code}</span>
<strong>{this.props.okane.USD.rate}</strong>
</li>
} else if (this.state.currency === 'GBP') {
list = <li className="list-group-item">
bitcoin rate for {this.props.okane.GBP.description}
:<span className='badge badge-primary'>{this.props.okane.GBP.code}</span>
<strong>{this.props.okane.GBP.rate}</strong>
</li>
} else if (this.state.currency === 'EUR') {
list = <li className="list-group-item">
bitcoin rate for {this.props.okane.EUR.description}
:<span className='badge badge-primary'>{this.props.okane.EUR.code}</span>
<strong>{this.props.okane.EUR.rate}</strong>
</li>
}
return(
<div>
<ul className="list-group">
{list}
</ul><br/>
<select onChange={e => this.setState({currency: e.target.value})} className="form-control">
<option value="USD">USD</option>
<option value="GBP">GBP</option>
<option value="EUR">EUR</option>
</select>
</div>
);
}
}
export default Prices;
さっきのliをlistという変数に入れてます。
そして「stateのcurrencyがUSDだったらこのli出してね」というif文にしています。
下の方にselect文を書いています。
onChange={e=>this.setState({currency: e.target.value})}で、
このselectのoptionが選択されるたびにそのvalueがe.target.valueで取れるので、
それをthis.setState({XXX:YYY})で上に書いたstateの値を入れ替えるという機構を用いて、動的にstateの値を変えました。
その結果、
成果物としてはこのようになりました。
それっぽいのができました。
本当にSSRされてるのかいな..
されてますね。
ちなみにSPAでも最近はgoogleに読まれるからSEO的に問題ないという話
それはほぼ間違っていると言っていいのでご注意を。
実際に、難易度が同じくらいの、
サイトキーワードを変えたサイトを5個ずつ作りましょう。
5つはSPAで、5つはSSRで作りましょう。
はい、SSRなしのSPAはgoogleに認識はされますが上位には来ません。
■コンソールで見ると画面が表示されてるから問題ない
■googleクローラーも賢くなってきてSPAでも読んでくれるようになってる
確かにそうですが、
SEOは強くなりませんのでご注意を。
まとめ
- まずはpackage.jsonで使うモジュールを管理できるようにする
- ページ間の移動をLinkを使ってできるようにする
- cssを直接書くのは<style jsx>{` `}</style>ですよ。
- headを読み込めばいつもの<head>がかけるよ。
- classも変数もコンポーネントとして呼び出せるよ
- layout.jsの使い方覚えてね。
- 値の受け渡しはpropsの引数に入ってくるよ
- apiをコールしてjsonにしてpropsに渡せば安心だよ
- stateを利用して動的に変更できるよ。
ですね。