こんにちは
DROBE のエンジニアの小黒です
直近のプロジェクトで Next.js を利用する機会があったので、DROBEでどのように利用したか簡単にご紹介したいと思います
Next.js の導入を検討している方の参考になれば幸いです
- Next.js 導入の背景
- Next.js について
- Next.js とは
- DROBE での Next.js の利用事例(開発編)
- ディレクトリ構造
- styled components
- サイトマップ
- API call
- next/Image コンポーネントについて
Next.js 導入の背景
DROBE では、AI とスタイリストがお客様一人一人の要望や好みに合わせて商品とコーディネートを提案させていただいているのですが、
ある時、マーケティングなどで利用するために、実際に取り扱っている商品やコーディネートの例を会員のお客様以外にも公開できる仕組みを作りたい、という話があがりました
PDM からもらった要件は下記の3つがあり
- 認証などの機能なしで閲覧できること
- SEO を考慮した Web ページになること
- drobe.jp ドメインで配信できること(既存サービスと同じドメイン配下におけること)
エンジニア内で話し合ったところ、下記の観点を重視して、Next.js を使って SSG でページを生成し、静的ホスティングサービスを使ってページを公開する、という方針で開発することにしてみたのでした
- 障害時などに既存サービスへの影響が少ない
- サーバーの管理を行わなくても良い
- DROBE 内の別開発でも使っている React.js を使って作られたコンポーネントを使いまわせる
- 急な高トラフィックに強い
- ページ読み込みが早く、 SEO に強い
Next.js について
Next.js とは
vercel という hosting サービスを提供している会社が提供している React ベースのフレームワークで、色々といい感じに最適化してくれながら SSR / SSG の機能を提供してくれます
開発用の hot reload 機能付きのサーバー が付いていたり、 sass や typescript を default でビルドに使えたりと、開発環境も充実しており最高の開発体験を味わえます
deploy 先は当然 vercel が最も相性が良いので、特別な理由がなければ vercel を選びましょう(DROBE はインフラが AWS 上に構築されていたので AWS 上への deploy になっています)
この後の利用事例ではチュートリアルに掲載されている Next.js の知識はある前提でご紹介しますので、Next.js ...? という方はぜひチュートリアルを読んでから利用事例の章に進んでください
Next.js のチュートリアルは非常に素晴らしく、短時間で概念を理解できるのでオススメです
DROBE での Next.js の利用事例(開発編)
DROBE での Next.js を使った開発に関して、簡単にご紹介します
主に利用ライブラリなどについて触れます
ディレクトリ構造
そこまで複雑なプロジェクトではなかったこともあり、ディレクトリ構造は特にトリッキーなことはしていません
構造は下記のようになっています
.
├── api
├── components
│ ├── atoms
│ ├── molecules
│ ├── organisms
│ ├── pages
│ ├── quantums
│ └── templates
├── const
├── pages
│ ├── _app.tsx
│ ├── _document.tsx
│ └── xxxx.tsx
├── records
├── styles
└── util
特筆するべき点もないくらいにシンプルです
styled components
DROBE では styled components をよく用いているので、今回も用いることにしました
他のプロジェクトで用いられているコンポーネントを一部持ってくることを想定していたので導入は必須でした
styled components と Next.js では想定している動作環境が違うらしく babel.config.json を作って styled components がサーバーサイドでも動くように設定してあげる必要があるみたいです
{
"presets": [
"next/babel"
],
"plugins": [
[
"styled-components",
{
"ssr": true,
"displayName": true,
"preprocess": false
}
]
]
}
ただ、 styled components を用いたことによってページを開いたタイミングで、一瞬 css の適用がおそくなるという問題に遭遇しました
どうやら styled components 側で css を描画するタイミング が微妙に遅れてしまうことによって発生しているようでした。
いわゆる FOUC( Flash of Unstyled Content ) という事象らしいです
さすがにこれは見栄えが悪すぎるとのことで調べたところドキュメントに下記の記載がありました
ご丁寧にサンプルまで用意してくれていました
どうやらビルド時に事前に css を作成しておき、Head にスタイルを注入しておいてくれることによってこの問題を解消できるようでした
サンプルの通り _document.tsx
に下記のように記載することにしてみました
import Document from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
)
};
} finally {
sheet.seal();
}
}
}
その結果下記のように適用遅れがなくなりました
MaterialUI なども同じような問題に遭遇するため、一律この対応が必要なようです
サイトマップ
SEO を考慮した Web ページとのことで サイトマップは必須です
DROBE では sitemap の生成は next-sitemap を用いることにしました
難しい設定は一切なく、シンプルで非常に使いやすかったです
module.exports = {
siteUrl: process.env.SITE_URL,
generateRobotsTxt: true,
}
大量にページを生成する予定なので、 sitemap の分割なども検討しなくてはならないのですが、このライブラリは全部よろしくやってくれるみたいで非常に便利でした
API call
静的なページといいつつ、絶対に最新じゃなければならない情報(価格など)に関してはどうしてもサーバーサイドからとってきたい、という話になり SSG なものの、一部 API を叩くことにしました
DROBE では普段から使い慣れている axios を使って動的に情報を取ってきています
~ api/item.ts ~
export const getItem = ({ sku }) => {
return axios.get(`/items/${sku}`);
}
~ pages/item.tsx ~
export default function Page({ sku }) {
const [item, setItem] = useState(null)
useEffect(() => {
const func = async () => {
const response = await getItem({ sku })
setItem(response.data)
}
func()
}, [sku])
return <ItemPage item={item} />
}
特別変わったことはしておらず、 hook を使って api call → 取得結果を反映、という処理を page に記載しています