Next.js×Railsでデータを表示する。 

環境構築はこちらから
https://qiita.com/gabakugik/items/53dc8888f7085d15f4d8

今回Next.js(React)×Ruby on Railsを使いデータを表示させようと思います。

ツリー構造
```
|-- backend
|   |-- Dockerfile
|   |-- Gemfile
|   |-- Gemfile.lock
|   |-- README.md
|   |-- Rakefile
|   |-- app
|   |   |-- channels
|   |   |-- controllers
|   |   |-- jobs
|   |   |-- mailers
|   |   |-- models
|   |   `-- views
|   |-- bin
|   |   |-- bundle
|   |   |-- rails
|   |   |-- rake
|   |   `-- setup
|   |-- config
|   |   |-- application.rb
|   |   |-- boot.rb
|   |   |-- cable.yml
|   |   |-- credentials.yml.enc
|   |   |-- database.yml
|   |   |-- environment.rb
|   |   |-- environments
|   |   |-- initializers
|   |   |-- locales
|   |   |-- master.key
|   |   |-- puma.rb
|   |   |-- routes.rb
|   |   `-- storage.yml
|   |-- config.ru
|   |-- db
|   |   |-- migrate
|   |   |-- schema.rb
|   |   `-- seeds.rb
|   |-- lib
|   |   `-- tasks
|   |-- log
|   |   `-- development.log
|   |-- public
|   |   `-- robots.txt
|   |-- storage
|   |-- test
|   |   |-- channels
|   |   |-- controllers
|   |   |-- fixtures
|   |   |-- integration
|   |   |-- mailers
|   |   |-- models
|   |   `-- test_helper.rb
|   |-- tmp
|   |   |-- cache
|   |   |-- development_secret.txt
|   |   |-- pids
|   |   |-- restart.txt
|   |   |-- sockets
|   |   `-- storage
|   `-- vendor
|-- docker-compose.yml
`-- frontend
    |-- Dockerfile
    `-- app
        |-- README.md
        |-- app
        |-- components
        |-- next-env.d.ts
        |-- next.config.mjs
        |-- node_modules
        |-- package-lock.json
        |-- package.json
        |-- postcss.config.mjs
        |-- public
        |-- tailwind.config.ts
        |-- tsconfig.json
        |-- types
        `-- yarn.lock
```

まずはbackendでデータを作ります。

```
docker compose run backend bundle exec rails generate model Todo title:string content:text

docker compose run backend bundle install

docker compose run backend bundle exec rails db:migrate
```

```
rack-corsのインストール
gem 'rack-cors'のコメントアウトをはずす。
```

```config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:3001' # Next.jsを動作させているアドレスとポート番号

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
```
```
docker compose run backend bundle install
docker compose build
docker compose run backend bundle exec rails generate controller Todos
```

```app/controllers/todos_controller.rb
class TodosController < ApplicationController
  # GET /todos
  def index
    # 日付が新しい順に10件まで取得する
    @todos = Todo.all.order(created_at: :desc).limit(10)

    render json: @todos
  end
end
```

```config/routes.rb
Rails.application.routes.draw do
  resources :todos, except: [:new, :edit]
end
```

```db/seeds.rb
Todo.create(title: 'Todo 1', content: 'Todo 1 の内容')
Todo.create(title: 'Todo 2', content: 'Todo 2 の内容')
Todo.create(title: 'Todo 3', content: 'Todo 3 の内容')
```
```
docker compose run backend bundle exec rails db:seed
```

```
docker-compose up -d
```
```
http://localhost:3000/todos
でデータが表示されるか確認
```

次はフロント部分を作っていきます
```
docker compose run --rm frontend npm install axios
docker compose run --rm frontend npm install swr
```

styles/globals.cssをすべて削除。

```frontend/app/app/page.tsx
// トップページ
export default function Home() {
  return (
    <div className="flex flex-col justify-center items-center">
      トップページ
    </div>
  );
}

```

```
docker-compose up -d
でトップページと表示されればOK
```

```frontend/types/Todo.ts
export type TodoType = {
  id: number;
  title: string;
  content: string;
};
```

```frontend/components/Todo.tsx
import { TodoType } from '../types/Todo';

// Todo一つを表示するコンポーネント
const Todo = ({ todo }: { todo: TodoType }) => {
  return (
    <div className="focus:outline-none mb-7 bg-white p-6 shadow rounded">
      <div className="flex items-center border-b border-gray-200 pb-6">
        <div className="flex items-start justify-between w-full">
          <div className="pl-3">
            <p className="focus:outline-none text-lg font-medium leading-5 text-gray-800">
              {todo.title}
            </p>
          </div>
        </div>
      </div>
      <div className="px-2">
        <p className="focus:outline-none text-sm leading-5 py-4 text-gray-600">{todo.content}</p>
      </div>
    </div>
  );
};

export default Todo;
```

```frontend/components/Todos.tsx
"use client";

import useSWR from 'swr';
import { TodoType } from '../types/Todo';
import Todo from './Todo';
import Link from 'next/link';

// Fetcher function to get data from the API
const fetcher = (url: string) => fetch(url).then(res => res.json());

const Todos = () => {
  // Use SWR to fetch and cache the Todo list
  const { data: todos, error } = useSWR<TodoType[]>('http://localhost:3000/todos', fetcher);

  if (error) return <div>Failed to load</div>;
  if (!todos) return <div>Loading...</div>;

  return (
    <div className="space-y-6 w-3/4 max-w-lg pt-10">
      <label className="block text-xl font-bold text-gray-700">Todo Index</label>
      <div className="items-center justify-center">
        {todos.map((todo) => (
          <Link
            href={`todos/${todo.id}`}
            key={todo.id}
          >
            <Todo todo={todo} />
          </Link>
        ))}
      </div>
    </div>
  );
};

export default Todos;


```

```frontend/app/app/page.tsx
import Todos from '@/components/Todos';

// トップページ
export default function Home() {
  return (
    <div className="flex flex-col justify-center items-center">
      <Todos />
    </div>
  );
}
```

これで３件のデータが表示されます。