みかづきブログ・カスタム

基本的にはちょちょいのほいです。

Next.js 13.4 + styled-componentsでApp Routerを使いつつ、outputをexportに設定して静的サイトジェネレータとして使う 💻

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;

nextjs.org

また、trailingSlashやdistDirの設定も行えます。

静的書き出し時のnext/link挙動

nextjs-ja-translation-docs.vercel.app

静的書き出しを行うと、稀に、next/linkをクリックした際に.txtに遷移することがあり、原因を調査したのですが結局わかりませんでした。
GitHubにも issue が投稿されています。

github.com

なんとか再現を試みるべく、試行錯誤してみたところ、hrefに相対パスを渡すと再現することがわかりました。
Next.jsがdocumentに設定しているclickイベントを剥がすと、遷移が成功するので、内部で何かやっていることが原因かと思います。

DEMO

develop.kimizuka.org

issue へのコメントをみるに、

  • App Routerのexportはまだサポートされていない
  • Next.js 13.2を使うべし

と書かれていますが、

とのことから、Next.js 13.3のときに立てられたissueなのではないかと疑っています。
が、一定の条件を満たすと.txtにリンクされてしまう問題は13.4でも残っているようです。

nextjs.org
nextjs.org

現状、絶対パスで指定すれば大丈夫そうではあります。

styled-components

nextjs.org

ドキュメントに 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' を使いましょう。

nextjs.org

❸ 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 を参考にして諦めました。

nextjs.org
github.com

/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です。

DEMO

develop.kimizuka.org


今回は以上です。
実装にあたっては、ドキュメント は当然ですが、こちらの サイトリポジトリ がとても参考になりました。

nextjs.org
github.com
app-dir.vercel.app