Next.js 13.4からApp RouterがStableになりました。
普段、Next.jsを静的サイトジェネレータとして使っている自分としては、App Routerの恩恵は少ないような気がしていたのですが、App Routeをつかっても静的書き出しができることを知ったので、これからは時代に合わせて、App Routerを活用していこうと思います。
個人的に、styled-componentsを使って実装することが多いので、styled-componentsの導入方法も調べます。
静的書き出し(Static Exports)
next exportコマンドがなくなり、next.config.jsのoutputをexportに設定の上、next buildをで静的書き出しを行うように変更されました。
next.config.js
/** * @type {import('next').NextConfig} */ const nextConfig = { output: 'export', // Optional: Add a trailing slash to all paths `/about` -> `/about/` // trailingSlash: true, // Optional: Change the output directory `out` -> `dist` // distDir: 'dist', }; module.exports = nextConfig;
また、trailingSlashやdistDirの設定も行えます。
DEMO
静的書き出し時のnext/link挙動
nextjs-ja-translation-docs.vercel.app
静的書き出しを行うと、稀に、next/linkをクリックした際に.txtに遷移することがあり、原因を調査したのですが結局わかりませんでした。
GitHubにも issue が投稿されています。
なんとか再現を試みるべく、試行錯誤してみたところ、hrefに相対パスを渡すと再現することがわかりました。
Next.jsがdocumentに設定しているclickイベントを剥がすと、遷移が成功するので、内部で何かやっていることが原因かと思います。
DEMO
issue へのコメントをみるに、
- App Routerのexportはまだサポートされていない
- Next.js 13.2を使うべし
と書かれていますが、
- issueの投稿日が4月30日( Next.js 13.4のリリースが5月5日 )
- App Routerのドキュメントに Static Exports の項目がある
- 戻すバージョンが13.2
とのことから、Next.js 13.3のときに立てられたissueなのではないかと疑っています。
が、一定の条件を満たすと.txtにリンクされてしまう問題は13.4でも残っているようです。
現状、絶対パスで指定すれば大丈夫そうではあります。
styled-components
ドキュメントに Styled Components の項目があるので、その通りに設定すればOKです。
導入手順
❶ 必要なモジュールを用意する
yarn add styled-components
yarn add -D @types/styled-components
❷ next.config.jsを編集する
next.config.js
module.exports = { compiler: { // see https://styled-components.com/docs/tooling#babel-plugin for more info on the options. styledComponents: boolean | { // Enabled by default in development, disabled in production to reduce file size, // setting this will override the default for all environments. displayName?: boolean, // Enabled by default. ssr?: boolean, // Enabled by default. fileName?: boolean, // Empty by default. topLevelImportPaths?: string[], // Defaults to ["index"]. meaninglessFileNames?: string[], // Enabled by default. cssProp?: boolean, // Empty by default. namespace?: string, // Not supported yet. minify?: boolean, // Not supported yet. transpileTemplateLiterals?: boolean, // Not supported yet. pure?: boolean, }, }, }
適宜設定します。(ただし、まだサポートされていない項目もあります)
これだけでstyled-componentsを使えます。
styled-componentsを使うコンポーネントは 'use client' を使いましょう。
❸ StyledComponentsRegistryを用意する
ここまでで導入できてはいるのですが、一瞬スタイルが当たってない姿が見えてしまうのが嫌なので、StyledComponentsRegistryを用意します。
/src/styling/StyledComponentsRegistry.tsx
'use client'; import React, { useState } from 'react'; import { useServerInsertedHTML } from 'next/navigation'; import { ServerStyleSheet, StyleSheetManager } from 'styled-components'; export function StyledComponentsRegistry({ children, }: { children: React.ReactNode; }) { const [ styledComponentsStyleSheet ] = useState(() => new ServerStyleSheet()); useServerInsertedHTML(() => { const styles = styledComponentsStyleSheet.getStyleElement(); // ts: Property 'clearTag' does not exist on type 'ServerStyleSheet' // @ts-ignore styledComponentsStyleSheet.instance.clearTag(); return <>{ styles }</>; }); if (typeof window !== 'undefined') return <>{ children }</>; return ( <StyleSheetManager sheet={styledComponentsStyleSheet.instance}> { children as React.ReactElement } </StyleSheetManager> ); }
ドキュメント に記載されているものとほとんど同じです。
default exportを止めていたり、ts-ignoreが追加されていたりします。
ts-ignoreはどうにかならないかと思ったのですが、こちらの issue を参考にして諦めました。
/src/app/layout.tsx
import { StyledComponentsRegistry } from '@/styling/StyledComponentsRegistry'; export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( <html lang="ja"> <body> <StyledComponentsRegistry>{ children }</StyledComponentsRegistry> </body> </html> ); }
layout.tsxではStyledComponentsRegistryを読み込んでchildrenをラップします。
これでOKです。