Djangoの静的ファイルにReactを配置し、連携させる

# はじめに
デプロイを前提としたDjango × Reactアプリを構築する際、フロントエンドとバックエンドを同じサーバーに入れておく方がサクサク動くのでは？ということで、DjangoにReactを読み込ませる方法を採用しました。
しかし実際にやってみるとDjangoの静的ファイルの扱い方にハマったため、記事に残すこととしました。
初学者(特にDjango)のため、間違いがあればご指摘ください。

# 環境
・Python: 3.8.13
・Django: 4.1
・React: 18.2.0 (create-react-app)
・Typescript: 4.7.4
・Tailwindcss: 3.0.2

# ディレクトリ構成
プロジェクト名をdjango-react-appとし、その中に5つのディレクトリを置きます。
・Pythonの仮想環境(drf-restapi)
・Djangoプロジェクト(django-backend)
・Django静的ファイル用ディレクトリ(django-frontend)
・Rest API(api)
・React環境構築(react-app)

```:django-react-appの構成
├── django-backend
├── django-frontend
├── drf-restapi
├── api
└── react-app
```

```:/django-react-app/django-frontend
├── __pycache__
├── migrations
├── static
│   ├── bundle.js
│   ├── bundle.js.map
│   ├── index.html
│   ├── styles.css
│   └── styles.css.map
├── templates
│   └── main
│       └── index.html
├── __init__.py
├── apps.py
├── urls.py
└── views.py
```

```:/django-react-app/react-app
├── build
├── config
│   ├── jest
│   ├── webpack
│   ├── paths.js
│   └── webpack.config.js
├── node_modules
├── public
│   └── index.html
├── src
│   ├── app
│   ├── slices
│   ├── components
│   ├── App.tsx
│   └── index.tsx
├── .env
├── package-lock.json
├── package.json
├── tailwind.config.js
└── tsconfig.json
```

# Django
## Python仮想環境とDjangoプロジェクトの作成

まずはPythonの仮想環境を作り、有効にすることで、作成した仮想環境が使用できるようにします。
```:/django-react-app
python -m venv drf-restapi
source drf-restapi/bin/activate
```

続いて仮想環境をアクティベートした状態で、pipコマンドを用いてDjango及びrest_frameworkのインストールをしていきます。
```:/django-react-app
pip install django 
pip install djangorestframework
```

Djangoプロジェクトを作成します。
```:/django-react-app
django-admin startproject django-backend
cd ./django-backend
```

一度サーバーを立ち上げ、Djangoアプリが起動することを確かめます。
```:/django-react-app/django-backend
python manage.py runserver 0:8080
```
http://localhost:8000/ にアクセスし、"The install worked successfully! Congratulations!"の文字と共に、ロケットが打ち上がれば成功です。あとは作成したいアプリケーションの追加等をすれば開発をはじめることができます。

## Djangoアプリケーションの作成
### 1. Django templates及びstaticディレクトリ
今回の目標となる、DjangoにReactを読み込ませるためには、ReactアプリをDjango側の静的ファイルとして配置する必要があります。
もう少し具体的にすると、Djangoのtemplatesを活用し、Django側のindex.htmlから、静的ファイルとして格納したReactアプリのbuildファイルを読み込むことになります。

まずはDjango templates及び静的ファイル格納用にDjangoアプリを作成します。
```:/django-react-app
python manage.py startapp django-frontend
cd ./django-frontend
```

作成したDjangoアプリケーションの中にtemplates及びstaticディレクトリを作成し、templates/main内にindex.htmlを記述します。
```html:/django-react-app/django-frontend/templates/main/index.html
<!DOCTYPE html>

{% load static %}

<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" type="text/css" href="{% static 'js/styles.css' %}" />
    <title>Todo App</title>
</head>
<body>
    <div id="root"></div>
    <script type="text/javascript" src="{% static 'js/bundle.js' %}" ></script>
    {% csrf_token %}
</body>
</html>
```
テンプレートから静的ファイルを読み込むには、まず{% load static %}を定義し、その下で{% static %}タグを使ってjs,css等のURLを記述する必要があります。Reactアプリをbuildすることでindex.tsxをコンパイルし、bundle.js及びstyles.cssをstaticファイルとして生成します。それをこのindex.htmlが読み込んで、id="root"にマウントすることで、DjangoでReactを読み込むことができるようになります。

urlsの作成及びviewsの更新を行います。SPAのルーティングがある場合はurlsに追記していきます。
```py:/django-react-app/django-frontend/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name=''),
    path('another_page/', views.index),
]
```
```py:/django-react-app/django-frontend/views.py
from django.shortcuts import render

def index(request):
    return render(request, 'main/index.html')
```

### 2. API作成
API作成用にDjangoアプリを作成します。
```:/django-react-app
python manage.py startapp api
```
詳細なAPIの作成方法については省略します。

### 3. Djangoプロジェクトの編集
SPAのURLになったら、先ほどDjangoのtemplatesに作成したindex.htmlを表示させます。
```py:/django-react-app/django-backend/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls')),
    path('', include('django-frontend.urls')),
]
```

これで/admin/と/api/以外のパスはindex.htmlを表示させることができます。
そしてindex.htmlの場所をDjangoに教えてあげます。
```py:/django-react-app/django-backend/settings.py
...
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    'rest_framework',   #追加
    'api.apps.ApiConfig',   #追加
    'django-frontend.apps.ApiConfig',   #追加
...
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [
            os.path.join(BASE_DIR,'templates')  #index.html格納先
        ],
    },
]
...
```

# React
## Reactの環境構築
Create React App(CRA)をインストールしてReact及びTypescript環境を構築をします。
```:/django-react-app
npx create-react-app react-app --template typescript
cd react-app
```

環境構築が完了したら、Tailwind CSSをインストールします。今回はCSSにTailwind CSSを適用していますが、お好きなスタイリング手法を選択してください。
```:/django-react-app/react-app
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
```js:/django-react-app/react-app/tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",  //追記
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```
```css:/django-react-app/react-app/src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

次にフロントエンドの中身を作成します。
```html:/django-react-app/react-app/public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Django-React App</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
```
```tsx:/django-react-app/react-app/src/index.tsx
import React from 'react';
import App from './App';
import './index.css';

const container = document.getElementById('root')!;
const root = createRoot(container);

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
```
```tsx:/django-react-app/react-app/src/App.tsx
import React, { FC } from 'react'

const App: FC = () => {
  return (
    <div className="text-center text-5xl text-blue-600 mt-20">
      <p>This is a Django-React App</p>
    </div>
  )
}

export default App
```

ここまでがReactアプリとして機能しているか確認しておきましょう。ターミナルでnpm startコマンドを入力後、http://localhost:3000/ をブラウザで開き、下記画面が表示されていれば問題ありません。
![スクリーンショット 2022-09-04 20.33.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822149/f28223b9-9944-ce46-3c50-eff110e98159.png)

## Reactのbuild先をDjango側に設定
Reactのbuild先を変更するには、webpack.config.jsを編集する必要があります。CRAの場合、 webpack環境はデフォルトで隠蔽された構成ファイルとなっているため、このままでは編集ができません。
webpack環境を更新するためのコマンドの一つがnpm run ejectです。これはカスタマイズ用のコマンドであり、すべての構成とビルドの依存関係がプロジェクトに直接移動されます。ただし一度ejectしてしまうと、設定ファイルと依存関係を再度パッケージングすることができなくなるため注意が必要です。
今回はejectコマンドを実行します。
```:/django-react-app/react-app
npm run eject
```

隠れていたconfigディレクトリから以下のようにwebpack.config.jsを編集します。
```js:/django-react-app/react-app/config/webpack.config.js
module.exports = function (webpackEnv) {
  const isEnvDevelopment = webpackEnv === 'development';
  const isEnvProduction = webpackEnv === 'production';
  ...
  return {
    ...
    entry: 'src/index',  //buildするファイルの格納先
    output: {
      // The build folder.
      path: path.join(__dirname, '../../django-frontend/static'),  //build後ファイルの生成先
      // Add /* filename */ comments to generated require()s in the output.
      pathinfo: isEnvDevelopment,
      // There will be one main bundle, and one file per asynchronous chunk.
      // In development, it does not produce real files.
      filename: isEnvProduction
        ? 'bundle.js'  //build後のjsファイル名
        : isEnvDevelopment && 'bundle.js',
        ...
    },
    ...
    plugins: [
      isEnvDevelopment && new CaseSensitivePathsPlugin(),
      isEnvProduction &&
        new MiniCssExtractPlugin({
          // Options similar to the same options in webpackOptions.output
          // both options are optional
          filename: 'styles.css',  //build後のcssファイル名
          ...
        }),
        ...
    ],
    ...
  };
};
```

ここまでの設定が完了したら、Reactアプリをbuildします。
```:/django-react-app/react-app
npm run build
```

Django静的ファイル用ディレクトリ(django-frontend)のstatic内にbundle.jsとstyles.cssがそれぞれ作成されていることを確認します。

# DjangoとReactの結合を確認
再びルートディレクトリに戻り、サーバーを立ち上げてDjangoアプリを起動します。
```:/django-react-app/django-backend
python manage.py runserver 0:8080
```

http://localhost:8000/ にアクセスし、React側でnpm startした時と同じ画面が出ていれば、完了です。DjangoにReactを読み込ませることができています！

# まとめ
Djangoで静的ファイルを読み込むために、Django及びReactそれぞれに設定が必要だったため、エラー箇所の特定にも苦しみながらの作業となりました。特にReactのwebpack.configを初めて触ったので、ここに一番頭を悩まされましたが、作業を通してWebpackについての理解を深められた気がします。この記事が自分のような初学者の一助となれば幸いです。

また今回は、Reactでwebpack環境をカスタマイズするためにejectコマンドを使用しましたが、隠蔽されていたconfigファイルを元に戻せないことや、ソースファイルが見づらくなる等、公式では推奨されていないことがわかりました。他の方法としてreact-app-rewiredをインストールして設定を上書きしたり、マニュアルでイチからReact環境を構築することによる対応が可能なことを知ったので、次の機会では別の方法を試してみたいと思います。

# 参考文献
* [DjangoのページをReactで作る - Webpack4](https://qiita.com/sand/items/15da91117c680a618c2b) 
* [DjangoとReactの構築及びAPI連携について～構築編～](https://qiita.com/shiranon/items/8182445975dff4cf19a6) 
* [Django＋SPA（Nuxt.js）の環境をHerokuにデプロイしてみた](https://mizumotok.hatenablog.jp/entry/2020/10/30/120718)