web_bonsaiの日記

web開発の学習日記です。誰に見せるためでもないただの日記です。

Next.jsのfrontend/pages/index.tsxでAPIから値を取得する | Mac + Docker + Rails + Next.js その0034

参考にさせていただいたページ

fetchの第一引数について

CORSの設定について

はじめに

今回の私のポイントとしては以下のようなことでした。

  • fetchの第一引数にはどういうURL文字列を渡したら良いか
  • RailsのCORSの設定をどうやったら良いか

frontend/pages/index.tsxにgetStaticProps()を定義する

frontend/pages/index.tsxに以下の通り、getStaticProps()を定義して、Homeコンポーネントでpropsを受け取るようにしました。

〜略〜

export const getStaticProps = async () => {
  const response = await fetch('http://nginx:8000/api/tasks');
  const json = await response.json();

  return {
    props: json,
  };
};

const Home: NextPage = (props) => {
  console.log(props);

  〜略〜
}

export default Home

fetchの第一引数は "http://[サービス名]:[ポート番号]/api/tasks" みたいな感じで渡すと大丈夫でした。

RailsのCORSの設定

rails/Gemfileの編集

以下の行を追記しました。

gem 'rack-cors'

bundle install

% docker-compose run --rm app bundle install

build

Gemfileを編集したのでbuildし直します。

% docker-compose build

rails/config/initializers/cors.rbの作成とその記述内容

% vim rails/config/initializers/cors.rb

以下の通り記述しました。

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins "*"
    resource "*",
             headers: :any,
             methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

rails/config/environments/development.rbの編集

rails/config/environments/development.rbのendの内側の一番下にconfig.hosts << "nginx"と一行追記しました。

以下のようになります。

require "active_support/core_ext/integer/time"

Rails.application.configure do
  〜略〜
  # localhost:ポート番号では通信に失敗します。
  # fetchの引数に渡しているサービス名と合わせます。
  config.hosts << "nginx"
end

docker-compose restartする

railsのconfigを変更したので、設定反映のためにrestartします。

% docker-compose restart

ブラウザで確認してみる

以下の「httpのURL」と「httpsのURL」にブラウザでアクセスして、ブラウザのコンソールでpropsが確認できました。

rails/config/initializers/cors.rbを少し修正する

originsはワイルドカードじゃない方が良いと思うので、以下の通り修正します。

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '127.0.0.1:3000', 'localhost:3000'
    resource '*',
             headers: :any,
             methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

ブラウザで確認してみる

一度restartします。

% docker-compose restart

以下の「httpのURL」と「httpsのURL」にブラウザでアクセスして、ブラウザのコンソールでpropsが確認できたら完成かなと思います。

railsアプリケーションの「タスクの新規登録」が動かなくなっていたので直す

だいぶ後になってわかったことですが、CORSを設定したことによって動かなくなっていたようです。

大事なのでここに追記しておきます。

「タスクの新規登録」ページからPOSTすると以下のエラーが出るようになりました。

ActionController::InvalidAuthenticityToken (HTTP Origin header (https://web-bonsai.com) didn't match request.base_url (http://web-bonsai.com)):

【Rails/Nginx/ELB 】ActionController::InvalidAuthenticityToken HTTP Origin header (https://example.com) didn’t match request.base_url (http://example.com)の解決法|Rubinistを目指す新米エンジニアのTECH BLOG」を参考にさせていただいて解決しました。

以下の行を追記します。

proxy_set_header X-Forwarded-SSL on;

おまけ

上手くいかない場合1

Uncaught FetchError: request to http://localhost:8000/api/tasks failed, reason: connect ECONNREFUSED 127.0.0.1:8000 のようなエラーが表示されて上手くいかない場合は、fetchの第一引数が適切でないかもしれません。

上手くいかない場合2

FetchError: invalid json response body at http://nginx:8000/api/tasks reason: Unexpected token < in JSON at position 0 のようなエラーが表示されて上手くいかない場合は、以下のようにgetStaticPropsの中で console.log(response) して、responseを確認してみると良いと思います。

export const getStaticProps = async () => {
  const response = await fetch('http://nginx:8000/api/tasks');
  console.log('==============================')
  console.log(response)
  console.log('==============================')
  const json = await response.json();

  return {
    props: json,
  };
};

ターミナルを見ると以下のように403エラーが出力されていると思います。

==============================
Response {
  size: 0,
  timeout: 0,
  [Symbol(Body internals)]: {
    body: PassThrough {
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: true,
      [Symbol(kCapture)]: false,
      [Symbol(kCallback)]: null
    },
    disturbed: false,
    error: null
  },
  [Symbol(Response internals)]: {
    url: 'http://nginx:8000/api/tasks',
    status: 403,
    statusText: 'Forbidden',
    headers: Headers { [Symbol(map)]: [Object: null prototype] },
    counter: 0
  }
}
==============================

そんなときはRailsのCORSの設定が上手くいっていないとか、docker-compose restartを忘れてるのかもしれません。