Phestから乗り換えるための静的ジェネレーター探しで、GatsbyがうまくインストールできずにReact Staticを試すことに。その過程のメモです。
大した内容ではなく「React Staticの使い方」というほどマニュアル的なものでもないので、何かしら探されてる方は記事内のリンクから公式サイトに移動することをお勧めします。
なお古い時期の情報が役に立たなかったり無駄が多い状態になっている可能性もあり、当記事を含めて外部の内容は参考程度にした方が良いです。
[追記:2019.7.31]
当記事はV6の時点です。2019.7時点ではV7であり違いが出ているようなので、以下にメモ的に情報を残しています。
V7ではwithSiteDataがuseSiteDataに変更されたりしているため、V6の情報を見ても適宜読み替えが必要になると思います。
参考サイト
- react-static
GitHub以外にはなさそうなのでおそらく唯一の公式。実際にReact Staticを使っているサイトの例示一覧がありいくつかはGitでコードも確認できるが、書き方は古い模様(2019.1現在では参考にしない方がよいとされている作り方の可能性が高い)。 - jQueryを卒業したかった僕がReact StaticでReactをイチから学んでWebサイトを作った話
導入だけでなく設計思想的なものまで参考に。
コマンドなど
//最小限のインストールはこれだけでよい
$ npm install -g react-static
//プロジェクトの立ち上げ。
//質問2つの対話型でつくる。
//最初はプロジェクト名。既存のプロジェクトを動かす場合にはそのプロジェクト名をいれればよい。インストール後すぐにこれを実行するとmacではuser直下にフォルダができてそこがプロジェクトの場所になる。プロジェクト作成場所は変更可能。
//次にテンプレートを選択。この時には選択肢が出てキーボードの上下で移動して選べるので、公式が用意しているテンプレート名がわからなくても問題ない。
$ react-static create
メモ
- nodeのバージョン
-
- nodeのバージョンは10より下(9や8)
詳細は次項のエラーで触れますが、最新や10では関連するパッケージの影響でエラーが発生する可能性があるため。エラーがでたらnodeのバージョン変更を視野にいれた方がよいかもしれない。
- プロジェクトの場所を任意の場所に変更する
-
- 希望の場所までcdで移動してから、$ react-static create
希望の場所は、mkdirで作ったりデスクトップの新規フォルダでも作成しても可能な模様。
他方、以下のようにすれば現在のディレクトリを基準にさらにディレクトリを作ってプロジェクトが作成できる//以下は一つ目の質問事項 [? What should we name this project? 任意のディレクトリ名/プロジェクト名
- コマンド
-
公式ページでもちろん確認できるが、プロジェクトフォルダ内のpackage.jsonでも以下のように確認できる。
"scripts": { "start": "react-static start", "stage": "react-static build --staging", "build": "react-static build", "bundle": "react-static bundle", "export": "react-static export", "serve": "serve dist -p 3000 -s" }
なお、公式templateのbasicを使う場合には以下のような書き方ではないとエラーがでた。理由は不明。詳細は後述のエラーの項で。
//サーバー起動 $ npm run start //ビルド開始 $ npm run build
- template
-
? Select a template below... ❯ basic blank stress-test Local Directory... GIT Repository...
上記の上2つが選べるtemplate。3つ目は大量のページを生成する負荷テスト用の模様。Local DirectoryとGIT Repositoryは試せていないが名称からローカル内のファイルかGit内のファイルかを選んで適用するものと思われる。
参考にしたサイトには別のテンプレート名があったので、時期によってテンプレートの数や種類が変更になっている可能性あり。
- watchの終了方法
-
基本的すぎて書かれていないのか専用で用意されているわけではないのかわからないが、情報が見つけられなかったので試して以下で終了を確認。ただこの方法ではサーバーは終了するがそれだけなので、コンソールにはエラーが表示され続ける。止めるにはブラウザをリロードする必要がある。
- control+C
- ローカルでのサイト表示
-
- http://localhost:3000
上記で表示できるが、プロジェクトフォルダ内のpackage.jsonのserverを書き換えれば変更可能かもしれない(今のところ必要性がないので未確認)。
ファイル構成
- 簡易にページを作成する場合
-
- src/Pagesにページ用のファイルを必要なだけ追加していく
コンポーネント
- 根幹となるコンポーネント
-
上記で以下のコンポーネントの説明を記載。
- Root
- Routes
- RouteData
- SiteData
- Head
- Prefetch
- SiteData(またはRouteData)からの値の取り出し方
-
//static.config.jsでtitle,metaDescription,subTitleを設定した場合 //withSiteDataを使う(RouteDataも同様) export default withSiteData(({ title,metaDescription,subTitle }) => ( <p>{title}{metaDescription}{subTitle}</p> ) //SiteDataを使う(RouteDataも同様) export default () => ( <SiteData> {({ title, metaDescription, subTitle }) => ( <p>{title}{metaDescription}{subTitle}</p> )} </SiteData> ) //入れ子にしてもよい export default () => ( <SiteData> {({ title, metaDescription, subTitle }) => ( <RuteData> <p>{title}{metaDescription}{subTitle}</p> </RuteData> )} </SiteData> )
<SiteData>を使う方法であれば、return内で記載したコンポーネントに入れた値をpropsで取り出しつつ<SiteData>の値も使えるため便利な場合もある。後述の「titleタグの設定(React-Helmetを使う)」の項目がこれに該当。
headの設定方法の検討
- headの設定(React-Helmetを使う)
-
headの設定にはReact Static内にすでに入っている React-Helmet libraryを使う。
以下はbasicのtemplateを前提としてApp.jsを使った場合の例。<head>の位置はどこに書いても構わない模様。
import React from 'react' import { Root, Routes } from 'react-static' import { Link } from '@reach/router' import { Head } from 'react-static' import './app.css' function App() { return ( <Root> <Head> <meta charSet="UTF-8" /> <title>This is my page title!</title> </Head> <nav> <Link to="/">Home</Link> <Link to="/about">About</Link> <Link to="/blog">Blog</Link> </nav> <div className="content"> <Routes /> </div> </Root> ) } export default App
優先度が存在するので、例えばApp.jsとcontainers/Post.jsに対して同時に<Head>を記述するとcontainers/Post.jsの方が優先される。
- headの設定(Documentを使う)
-
以下のようにstatic.config.jsにDocumentを追加する。
//Documentを使う場合にはReactを読み込ませないとエラーになる import React from 'react' export default { getSiteData: () => ({ title: 'React Static', }), getRoutes: async () => { //省略 }, Document: ({ Html, Head, Body, children, siteData, renderMeta }) => ( <Html lang="UTF-8"> <Head> <meta charSet="en-us" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> </Head> <Body>{children}</Body> </Html> ), }
コメントで書いているがDocumentを使う場合にはReactを読み込ませないとエラーになるので必ず書くこと。
Bodyなどいずれかを削除するとエラーになってコードを元に戻すだけでは直らない。一度control+Cで落としてから再度$ npm run startをする必要がある。
- titleタグの設定(React-Helmetを使う)
-
ブログ型ではなく個別ページとしてJSファイルでページを追加する場合、またはマークダウンを併用する場合(「react-static-plugin-markdownの使い方」を参照のこと)、全体に対する反映方法だけではtitleやdescriptionの対応に一手間必要になる。
static.config.jsのgetRouteで全ページの設定を行う方法もあるが、以下のように各ページのJS内に記載する方法もある。
//省略 export default { getSiteData: () => ({ title: 'サイトのタイトル', description: 'サイトのdescription', }), //省略
まず上記のようにstatic.config.jsにサイトのタイトルなどを設定。
import React from 'react' import { Head, SiteData } from 'react-static' export default (props) => ( <SiteData> {({title}) => ( <Head> <title> { props.pageTitle ? props.pageTitle + ' - ' + title : title } </title> </Head> )} </SiteData> );
上記のようにmeta用のコンポーネントを別ファイルで作成。propsの値は後述するページ用JSファイルから得る前提で、値の有無で以下のように表示を変更する。
- 値があれば、それを使って「ページタイトル – サイトタイトル」で表示
- 値がなければ、それを使って「サイトタイトル」で表示
import React from 'react' import { withSiteData } from 'react-static' import HeadMeta from './headMeta' export default withSiteData() => ( <div> <HeadMeta pageTitle="ページJS側で設定するページタイトル" /> <h1>H1タイトル</h1> <p>本文</p> </div> ))
上記がそのページ用JS例。pageTitleを記載した場合は前述の1が、記載しない場合は2の形で出力される。
- CSSの追加と削除
-
//App.jsのimportを削除すればCSSは読み込まれない //import './app.css' //importを増やせばmain.cssとして一つに統合されてlink要素に設定される import './app.css' import './heading.css'
basicのtemplateではReact-Helmet libraryが自動で効いているため、 React-Helmet libraryを前提として前もって設定されている項目を変更したい場合は一手間かかる印象。
その他
- Linkがアクティブの時に装飾をつける
-
<a aria-current="page" href="">アンカーテキスト</a>
<Link to="">でリンクを設定した場合、toの指定とページのアドレスが一致していればaria-current="page"という属性が追加される。これを利用して現在アクティブなリンクのための装飾が設定可能。
- Chromeなどでfaviconがないと警告が出る
-
example.com/favicon.ico 404
時間経過やリロードで上記のような警告がでる。実際のブラウザでの表示には問題がないが、このアラートを消したい場合は以下のようにすると消える。
- publicフォルダにもfaviconをいれる
実際には使わないので全くの無駄ではあるが、ブラウザの認識的に正しい形として認識されるならやむなしという方法。
- AdSenseの設置
-
基本的にはreact-adsenseを使って表示させるのが早い模様。おそらく以下のように//pagead2.googlesyndication.com/pagead/js/adsbygoogle.jsを読み込む記述がないと表示されない。
{// static.config.jsでDocumentを用いてheadにmetaなどを出力している場合 } Document: ({ Html, Head, Body, children, siteData, renderMeta }) => ( <Html lang="UTF-8"> <Head> <meta charSet="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> </Head> <Body>{children}</Body> </Html> ),
また、未確認だがルートが変更されたときにAdSenseユニットを更新する方法というのもあったので参考に。
<AdSense.Google key={'300x250-'+post.slug} client='ca-pub-7292810486004926' slot='7806394673' />
- Google Analyticsの導入(未解決)
-
- Google Analytics + React Static v6
- How to handle router events (for Google Analytics)?
- react-static-plugin-google-analytics
- Track page views between routes #1
基本的には1つ目のページで状況がまとまっている模様。このページの中で紹介されている上記2つ目のページ内の方法で動くらしいが、うまく組み込めていないのか動かせず。
react-static用のプラグインとして3つ目のページのものがあるが、Installationに沿って記述を加えても動かず。static.config.jsのDocumnetに直接プラグインのindex.jsの記述を行えば一応動く。
const scriptUrl = `https://www.googletagmanager.com/gtag/js?id=GAのIDを記述`; const gTag = ` window.dataLayer = window.dataLayer || []; function gtag() { window.dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'GAのIDを記述'); `; export default { //省略 Document: ({ Html, Head, Body, children, siteData, renderMeta }) => ( <Html lang="UTF-8"> <Head> <script async src={scriptUrl}/> <script dangerouslySetInnerHTML={{__html: gTag}}/> <meta charSet="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> </Head> <Body>{children}</Body> </Html> ), //省略
ただしこの状態では最初のアクセスを検知して動作するだけで、別ページに移動した際は検知できない。この問題は4つ目のページにあるように作者も把握しているが、react-static側にフックを用意してもらう必要があるという考えの模様。
なお、Routerが必要な時点でReactを使った同種の構造を持つサイトではこの問題は一般的なものと思われる。
- マークダウンの利用
-
プラグインの利用で設定も簡単にできる。詳細は上記のページに記載。
エラー
- $ react-static start後のサイト表示でエラーがでる
-
“Oh-no! Something’s gone wrong!”
templateでbasicを選択した場合、$ react-static start後にhttp://localhost:3000にアクセスすると、一瞬正常に表示された後で上記の文章とReloadボタンが表示される場合あり。個人的に確認したのは以下の点。
- $ react-static startではなく$ npm run startと記述して実行
公式では$ react-static startでありtemplateは公式そのままにもかかわらず$ react-static startでエラーがでる理由を突き止められず。
- $ react-static buildでエラーが出る
-
前項と同様に公式tenplateのbasicで、まったく手を加えていない状態から$ react-static buildをするとエラーになるが、$ npm run buildをするとエラーがでない。そのためこちらも以下のように書いて実行。
- $ react-static buildではなく$ npm run buildと記述して実行
- $ react-static creatでnode-gypがらみのエラーが出る
-
前項のエラー解決ためにnodeを最新にして試していたが、最新バージョンでは$ react-static createを実行すると以下のようなエラーがでるようになった。テンプレートの状態によるとは思うが、このエラーで動作が止まることはなくサーバーは通常どうり起動する。
Using React Static template: blank => Installing dependencies with: NPM... node-pre-gyp WARN Tried to download(404): https://fsevents-binaries.s3-us-west-2.amazonaws.com/v1.2.4/fse-v1.2.4-node-v67-darwin-x64.tar.gz node-pre-gyp WARN Pre-built binaries not found for fsevents@1.2.4 and node@11.6.0 (node-v67 ABI, unknown) (falling back to source compile with node-gyp) //中略 gyp: No Xcode or CLT version detected! //中略 gyp ERR! node -v v11.6.0 gyp ERR! node-gyp -v v3.8.0 gyp ERR! not ok
対策としてnode-gypがらみでnodeのバージョンを9に落とせばいいというのを見たため、nodebrewを使ってnodeのバージョンを8.5.0(または9.0?)に落とす。
ただ、nodeのバージョンを下げるべしという記述は散見されるもののその根拠がnode-gypの公式などで見つけられず。
- static.config.jsを触ってコンテンンツ部分が404
-
- 404 – Oh no’s! We couldn’t find that page 🙁
static.config.jsを触っていてエラーになった場合、コンテンツ部分に上記の文言がでてstatic.config.jsを元に戻してもそのまま直らないことが多々ある。ブラウザのリロード(Chromeなら長押しのキャッシュ削除でも)だけでは直らない。
一旦control+Cで切ってから、再度$ npm run startで立ち上げた上でブラウザをリロードさせればほぼ直る。
- Chromeの「ハード再読み込み」を行うとエラー
-
- http://localhost:3000/favicon.ico 404 (Not Found)
$ npm run startでサーバーを立ち上げたあと、表示されたサイト内のページで「ハード再読み込み(キャッシュの消去とハード再読み込み)」を行うと上記のエラーがコンソールに表示される。
そもそもfaviconの設定はしていない状態で記述も見当たらないので原因特定できていないがfaviconだけがエラーになる。
サーバーの再起動か単なるリロードを行うとこのエラーを消せるが、何れにしても内部に問題が残り続けていないとは言い切れない状態。
- サブディレクトリに設置できない
-
buildしたファイルをサーバーにあげる際に、サブディレクトリ入れるとパスが狂う。
具体的にはいれたリンクをクリックした際に、サブディレクトリの部分が削られたURLに変更されてしまう。- /sub/にいれた場合。hrefがsub/exapmleでも/exampleに変更されて移動させられる
ファイル構成をサブディレクトに合わせて画像やCSSのリンクを繋ぐ設定はあるが、このURLを変更する設定が見つけられず。できないはずはないが現状では対応策が見つからなかった。
結び
とりあえず触ってみようという段階からなので、後ほど内容を書き換える、あるいは記事自体を消す(あまりにも見当違いな場合は消した方がよいので…)可能性があります。
0人がこの記事を評価
役に立ったよという方は上の「記事を評価する」ボタンをクリックしてもらえると嬉しいです。
連投防止のためにCookie使用。SNSへの投稿など他サービスとの連動は一切ありません。