LoginSignup
574
572

More than 3 years have passed since last update.

Next.js+TypeScriptでシンプルなニュースサイトを作ってみた。

Last updated at Posted at 2021-02-10

作ったもの

スクリーンショット 2021-02-10 0.27.10.png

タイトルはSimple Newsです。
サイトURLは、 こちらです。
ソースコードは こちらです。

Google News を参考にしました。

どんな機能があるの?

  • NewsAPIからニュースサイトの記事のデータを取得し、タイトルと画像を表示
    • NewsAPIは、世界中のニュースサイトから検索をかけて、情報を取得できるAPI ( Application Programming Interface )
  • OpenWeatherMapから取得した、現在の天気情報と5日間の天気予報を表示

    • OpenWeatherMapは、現在の気象やある期間の気候の予測データを取得できるAPI
  • ページ遷移が高速

ezgif-2-ad5e5eae6103.gif
記事をクリックすると、記事が掲載されているサイトへ飛びます。
こんな感じでとてもシンプルで高速に動くニュースサイトです。

こんな人に読んでほしい

  • Next.jsを使ってみたい初心者
  • 外部APIを叩きたい人
  • React,TypeScriptの基本は理解できている人

何が学べる?

  • Next.jsの基本的な扱い方 (getStaticPropsを使った外部データの取得と表示の仕方やLinkコンポーネントを使ったページ遷移の仕方)
  • Vercelを使ったデプロイの仕方
  • APIの叩き方 (NewsAPI, OpenWeatherMap)

プロジェクトを作成する前に

必要なAPIKeyを取得します。
※ NewsAPIとOpenWeatherMapのAPIKeyの取得の仕方が分かる方は、読み飛ばして構いません。

NewsAPIのAPIKeyを取得する

NewsAPIにログインします。

アカウントのメールアドレスとパスワードを設定します。
スクリーンショット 2021-02-09 23.06.34.png

これでNewsAPIのAPI Keyを取得することができたので、メモを取っておいて下さい。
スクリーンショット 2021-02-09 23.06.52.png

OpenWeatherMapのAPIKeyを取得する

OpenWeatherMapにログインします。
スクリーンショット 2021-02-09 22.46.29.png
アカウントのメールアドレスとパスワードを設定します。
スクリーンショット 2021-02-09 22.47.08.png
ユーザーネームをクリックし、My API Keysを選択します。
スクリーンショット 2021-02-09 22.55.57.png
Keyの下に、API Keyが表示されているはずなので、メモを取っておいて下さい。
スクリーンショット 2021-02-09 22.57.29.png

環境構築

create-next-appの実行、必要なパッケージのインストール

まず初めに、create-next-appでプロジェクトを作成します。

$ npx create-next-app <プロジェクト名>

その後に、必要なパッケージをインストールしていく。

npmなら

$ npm install sass moment
$ npm install --dev @types/node @types/react typescript

yarnなら

$ yarn add sass moment
$ yarn add -D @types/node @types/react typescript

TypeScriptとSass(scss)を導入

tsconfig.jsonnext-env.d.tsファイルをルートディレクトリに作成します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}
next-env.d.ts
/// <reference types="next" />
/// <reference types="next/types/global" />

これでTypeScriptとSass(scss)を導入することができました。
pageディレクトリのapiファイルは今回削除します。

_app.js => _app.tsx
index.js => index.tsx
globals.css => globals.scss
Home.module.css => Home.module.scss

importの部分も変更して下さい。

それぞれの拡張子を.tsx,.scssにしたら、

$ npm run dev

を実行して動作確認をします。( localhost:3000 を開く)

スクリーンショット 2021-02-10 9.52.20.png

多分こんな感じになります。

必要なファイルとフォルダを作成

ルートディレクトリにsrcフォルダを作成し、下のフォルダを入れます。
pages,layouts,styles,components

globals.scssの設定

src/styles/globals.scss
* {
  background-color: rgb(31, 31, 31);
  color: white;
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
  box-sizing: border-box;
}

a {
  color: inherit;
  text-decoration: none;
}

publicフォルダ

publicフォルダはルートディレクトリ直下のままで大丈夫です。

中身はナビのアイコンや天気のアイコンです。必要でしたらこちらからダウンロードして下さい。
iconはflaticonというフリーアイコンサイトからダウンロードしました。

レイアウト

全体のレイアウトを整えるためにレイアウトコンポーネントを作成します。
layoutsフォルダにindex.tsxindex.module.scssを作成します。

src/layouts/index.tsx
import Header from "../components/header"
import styles from "./index.module.scss";

type LayoutProps = {
  children: React.ReactNode;
};

function MainLayout({ children }: LayoutProps): JSX.Element {
  return (
    <>
      <Header />
      <main className={styles.main}>{children}</main>
    </>
  );
}

export default MainLayout;
src/layouts/index.module.scss
// Hederコンポーネントの分上にmarginをとります。
.main {
  margin-top: 60px;
}

これでメインのレイアウトが決まりました。

Headerコンポーネント

次にヘッダーを作るためにHeaderコンポーネントを作成します。

src/components/header/index.tsx
import styles from "./index.module.scss";
import Image from "next/image";
import Link from "next/link";

function Header() {
  return (
    <section className={styles.container}>
      <header className={styles.header}>
        <div className={styles.header__icon} >
          <Image
            src="/img/headerIcon/menu.png"
            alt="menu icon"
            loading="eager"
            width={35}
            height={35}
            priority
          />
        </div>
        <h1 style={{ letterSpacing: "1px", textAlign: "left" }}>
          <Link href="/">
            <a>
              <span style={{ fontWeight: 250 }}>Simple</span>
              <span style={{ fontWeight: 100 }}>News</span>
            </a>
          </Link>
        </h1>
      </header>
    </section>
  );
}

export default Header;
src/components/header/index.module.scss
.container {
  padding: 10px;
  height: 60px;
  position: fixed;
  top: 0;
  width: 100%;
  color: white;
  z-index: 1;
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
}

.header {
  height: 53px;
  vertical-align: middle;
  white-space: nowrap;
  align-items: center;
  display: flex;
}

.header__icon {
  display: inline-block;
  vertical-align: middle;
  height: 50px;
  width: 50px;
  flex: 0 0 auto;
  border-radius: 50%;
  padding: 14px;
  &:hover {
    background: rgba(77, 77, 77, 0.6);
  }
}

ここまで来たらもう一度localhost:3000 を立ち上げます。

$ npm run dev

そうするとこのような画面になると思います。
スクリーンショット 2021-02-10 10.27.46.png
まだヘッダーだけなので寂しいですよね(笑)
ここからどんどんコンポーネントを書いていきます。

Articleコンポーネント

NewsAPIで取得した記事たちを表示するコンポーネント

Next.jsのデータフェッチについて

Next.jsではgetStaticPropsを使うことで、ビルド時に外部からのデータなどを事前にフェッチすることができます。これがSSG (Static Site Generations)です。正直自分にとって理解するのに難しいと思いました。
revalidateとは、設定した時間(ここでは60×10秒)が経ち、リクエストががあって初めて再生成される。これがいわゆるISR (Incremental Static Regeneration)です。これでSSGのページを更新することができます。

スクリーンショット 2021-02-10 18.53.55.png

参考文献:Next.js公式チュートリアルより

NewsAPIで記事を取得

まずはgetStaticPropsで記事を取得し、propsに返り値としていきます。

src/pages/index.tsx
import Head from 'next/head'
import MainLayout from '../layouts'
import styles from '../styles/Home.module.scss'

export default function Home(props) {
  // 記事を取得できているか確認
  console.log(props.topArticles)
  return (
    <MainLayout>
      <Head>
        <title>Simple News</title>
      </Head>
    </MainLayout>
  )
}


export const getStaticProps = async () => {
  // NewsAPIのトップ記事の情報を取得
  const pageSize = 10   // 取得したい記事の数
  const topRes = await fetch(
    `https://newsapi.org/v2/top-headlines?country=jp&pageSize=${pageSize}&apiKey=あなたのNewsAPIのAPIKey`
  );
  const topJson = await topRes.json();
  const topArticles = topJson?.articles;

  return {
    props: {
      topArticles,
    },
    revalidate: 60 * 10,
  };
};

ChomeDevToolsを開くと、配列で10個のオブジェクトが取得できていると思います。

スクリーンショット 2021-02-10 10.42.26.png

※できなかった場合は、もう一度コンソールで npm run dev を実行してみたら、取得できるかもしれません

コンポーネントを作成する前に

typeを作成します。

src/components/types.ts
type Props = {
  articles?: [
    article: {
      author: string;
      title: string;
      publishedAt: string;
      url: string;
      urlToImage: string;
    }
  ];
  title?: string;
  weatherNews?: {
    current: {
      temp: number;
      clouds: number;
      weather: [
        conditions: {
          main: string;
          icon: string;
        }
      ];
    };
    daily: [
      date: {
        dt: number;
        clouds: number;
        temp: {
          min: number;
          max: number;
        };
        weather: [
          conditions: {
            id: number;
            icon: string;
          }
        ];
      }
    ];
  };
};

export default Props

Articleコンポーネントの作成

src/components/article/index.tsx
import styles from "./index.module.scss";
import moment from "moment";
import Props from "../types";

const Article: React.FC<Props> = ({ articles, title }) => {
  return (
    <section className={styles.article}>
      <div className={styles.article__heading}>
        <h1>{title.charAt(0).toUpperCase() + title.slice(1).toLowerCase()}</h1>
      </div>
      {articles.map((article, index) => {
        const time = moment(article.publishedAt || moment.now())
          .fromNow()
          .slice(0, 1);
        return (
          <a href={article.url} key={index} target="_blank" rel="noopener">
            <article className={styles.article__main}>
              <div className={styles.article__title}>
                <p>{article.title}</p>
                <p className={styles.article__time}>
                  {time}
                  時間前
                </p>
              </div>
              {article.urlToImage && (
                <img
                  key={index}
                  src={article.urlToImage}
                  className={styles.article__img}
                  alt={`${article.title} image`}
                />
              )}
            </article>
          </a>
        );
      })}
    </section>
  );
};

export default Article;
src/components/article/index.module.scss
.article {
  margin: 10px auto;
}

.article__heading {
  display: flex;
  justify-content: space-between;
  margin: 20px;
}

.article__main {
  display: flex;
  border: 1.2px solid rgba(135, 135, 135, 0.5);
  margin: 0 20px 20px 20px;
  padding: 15px;
  border-radius: 10px;
}
.article__title {
  flex: 4;
  margin-right: 5px;
}

.article__time {
  opacity: 0.5;
}

.article__img {
  object-fit: contain;
  width: 100%;
  border-radius: 5px;
  max-height: 100px;
  margin-right: 10px;
  flex: 1;
}

Articleコンポーネントの表示

src/pages/index.tsxにArticleコンポーネントを加えます。

src/pages/index.tsx
import Head from 'next/head'
import MainLayout from '../layouts'
import styles from '../styles/Home.module.scss'
import Article from '../components/article'

export default function Home(props) {
  console.log(props.topArticles)
  return (
    <MainLayout>
      <Head>
        <title>Simple News</title>
      </Head>

      // Articleコンポーネントを追加
      <div className={styles.main}>
        <Article title="headlines" articles={props.topArticles} />
      </div>
    </MainLayout>
  )
}


export const getStaticProps = async () => {
  // NewsAPIのトップ記事の情報を取得
  const pageSize = 10;
  const topRes = await fetch(
    `https://newsapi.org/v2/top-headlines?country=jp&pageSize=${pageSize}&apiKey=あなたのNewsAPIのAPIKey`
  );
  const topJson = await topRes.json();
  const topArticles = topJson?.articles;

  return {
    props: {
      topArticles,
    },
    revalidate: 60 * 10,
  };
};

これで

スクリーンショット 2021-02-10 11.00.23.png

取得した記事を一覧として表示できたかと思います。

Navコンポーネント

トピックごとのページに飛ぶためのナビバーを作成していきます。

<Link> について

<Link>はNext.jsでルーティング用のコンポーネントです。クライアントサイドでルーティングを可能にします。

Navコンポーネントの作成

src/components/nav/index.tsx
import Link from "next/link";
import styles from "./index.module.scss";
import Image from "next/image";

const TOPICS = [
  {
    icon: "01",
    path: "/",
    title: "Top stories",
  },
  {
    icon: "03",
    path: "/topics/business",
    title: "Business",
  },
  {
    icon: "04",
    path: "/topics/technology",
    title: "Technology",
  },
  {
    icon: "05",
    path: "/topics/entertainment",
    title: "Entertainment",
  },
  {
    icon: "06",
    path: "/topics/sports",
    title: "Sports",
  },
];

const Nav: React.FC = () => {
  return (
    <section className={styles.container}>
      <ul className={styles.contents}>
        {TOPICS.map((topic, index) => {
          return (
            <li key={index} >
              <Link href={`${topic.path}`}>
                <a>
                  <span>
                    <Image
                      src={`/img/navIcons/${topic.icon}.png`}
                      alt={`${topic.title} icon`}
                      loading="eager"
                      width={33}
                      height={33}
                      priority
                    />
                  </span>
                  <span>{topic.title}</span>
                </a>
              </Link>
            </li>
          );
        })}
      </ul>
    </section>
  );
};

export default Nav;

src/components/nav/index.module.scss
.container {
  width: 100%;
  padding: 20px 0 0 20px;
}

.contents {
  list-style-type: none;
  >li {
    margin-bottom: 15px;
    opacity: 0.7;
    color: rgb(196, 196, 196);
    >a {
      display: flex;
      >span {
        margin-left: 15px;
        line-height: 36px;
      }
    }
    &:hover {
      opacity: 1.0;
    }
  }
}

Next.jsの<Link>を使うことで、ページの高速移動を実現させます。

Navコンポーネントの表示

src/pages/index.tsx
import Head from "next/head";
import MainLayout from "../layouts";
import styles from "../styles/Home.module.scss";
import Article from "../components/article";
import Nav from "../components/nav";

export default function Home(props) {
  return (
    <MainLayout>
      <Head>
        <title>Simple News</title>
      </Head>
      <div className={styles.contents}>
        <div className={styles.nav}>
          <nav>
            <Nav />
          </nav>
        </div>
        <div className={styles.blank} />
        <div className={styles.main} >
          <Article title="headline" articles={props.topArticles} />
        </div>
      </div>
    </MainLayout>
  );
}

export const getStaticProps = async () => {
  // NewsAPIのトップ記事の情報を取得
  const pageSize = 10;
  const topRes = await fetch(
    `https://newsapi.org/v2/top-headlines?country=jp&pageSize=${pageSize}&apiKey=あなたのNewsAPIのAPIKey`
  );
  const topJson = await topRes.json();
  const topArticles = topJson?.articles;

  return {
    props: {
      topArticles,
    },
    revalidate: 60 * 10,
  };
};
src/styles/Home.module.scss
.contents {
  display: flex;
  margin: 0 auto;
}

.main {
  flex: 6;
}

.nav {
  flex: 2;
  position: fixed;
}

.blank {
  flex: 2;
}

結果、こんな感じになると思います。

スクリーンショット 2021-02-10 11.36.49.png

まだBusinessやTechnologyを押しても、ページは表示されません。

動的ルート (トピックス)

Next.jsでは[id].jsで、動的ルートを作成します。( [ ]鍵カッコの中身は任意です。)

src/pages/topics/[id].tsx
import Head from "next/head";
import { useRouter } from "next/router";
import Article from '../../components/article'
import Nav from '../../components/nav'
import MainLayout from "../../layouts/index";
import styles from "../../styles/Home.module.scss";

function Topic(props) {
  const router = useRouter();
  if (router.isFallback) {
    return <div>Loading...</div>;
  }

  return (
    <MainLayout>
      <Head>
        <title>Simple News - {props.title.toUpperCase()}</title>
      </Head>
      <div className={styles.contents}>
        <div className={styles.nav} >
          <nav>
            <Nav />
          </nav>
        </div>
        <div className={styles.blank} />
        <div className={styles.main} style={{marginRight:"10%"}}>
          <Article title={props.title} articles={props.topicArticles} />
        </div>
      </div>
    </MainLayout>
  );
}

export async function getStaticPaths() {
  return {
    paths: [],
    fallback: true,
  };
}

export async function getStaticProps({ params }) {
  const topicRes = await fetch(
    `https://newsapi.org/v2/top-headlines?country=jp&category=${params.id}&country=jp&apiKey=あなたのNewsAPIのAPIKey`
  );
  const topicJson = await topicRes.json();
  const topicArticles = await topicJson.articles;

  const title = params.id;

  return {
    props: { topicArticles, title },
    revalidate: 60 * 10,
  };
}

export default Topic;

これでページ遷移が可能になりました。

スクリーンショット 2021-02-10 12.09.22.png

Weatherコンポーネント

OpenWeatherMapで取得した天気情報を表示するコンポーネントを作成していきます。

src/components/weather-news/index.tsx
import Image from "next/image";
import styles from "../weather-news/index.module.scss";
import Link from "next/link";
import Props from "../types";

const week = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

const WeatherNews: React.FC<Props> = ({ weatherNews }) => {
  const currentWeatherMain = weatherNews.current.weather[0].main
  const currentWeatherTemp = weatherNews.current.temp
  const currentWeatherIcon = weatherNews.current.weather[0].icon.slice(0, 2) + "d";
  return (
    <section className={styles.weather}>
      <h1>Tokyo</h1>
      <div className={styles.weather__main}>
        <div className={styles.weather__top}>
          <div className={styles.weather__heading}>
            <a>{currentWeatherMain}</a>
            <p>
              {currentWeatherTemp.toString().slice(0, 1)}
              <span>˚c</span>
            </p>
          </div>
          <Image
            className={styles.weather__icon}
            src={`/img/weatherIcons/${currentWeatherIcon}.png`}
            alt="Tokyo's weather icon"
            loading="eager"
            width={52}
            height={52}
            priority
          />
        </div>
        <div className={styles.weather__weekly}>
          <ul className={styles.weather__weekly__list}>
            {weatherNews.daily.map((date, index) => {
              const time = new Date(date.dt * 1000);
              let day = week[time.getDay()];
              const nowDay = week[(new Date()).getDay()]
              if (day ==  nowDay) {
                day = "Today"
              }
              if (index > 4) {
                return;
              }
              return (
                <li key={index}>
                  <p>{day}</p>
                  <span>
                    <Image
                      src={`/img/weatherIcons/${date.weather[0].icon}.png`}
                      className={styles.weatehr__icon}
                      alt={`${day}'s weather icon`}
                      loading="eager"
                      width={41}
                      height={41}
                      priority
                    />
                  </span>
                  <div className={styles.weather__temp}>
                    <p className={styles.weather__temp__high}>
                      {parseInt(date.temp.max.toLocaleString(), 10)}˚c
                    </p>
                    <p className={styles.weather__temp__low}>
                      {parseInt(date.temp.min.toLocaleString(), 10)}˚c
                    </p>
                  </div>
                </li>
              );
            })}
          </ul>
        </div>
        <div className={styles.weather__bottom}>
          <Link href="https://weathernews.jp/onebox/">
            <a target="_blank" rel="noopener">
              ウェザーニュース
            </a>
          </Link>
        </div>
      </div>
    </section>
  );
};

export default WeatherNews;

src/components/weather-news/index.module.scss
.weather {
  border: 1.2px solid rgba(135, 135, 135, 0.5);
  border-radius: 15px;
  padding: 0 20px 0 20px;
  margin: 30px 20px 0 20px;
  h1 {
    font-weight: 1000;
    font-size: 16px;
    color: rgb(233, 163, 163);
    padding: 12px 0;
    border-bottom: 1px solid rgba(150, 113, 113, 0.5);
  }
}

.weather__top {
  display: flex;
  justify-content: space-between;
  padding-top: 20px;
  padding-bottom: 30px;
}

.weather__heading {
  a {
    font-weight: 20;
  }
  p {
    font-weight: 400;
    font-size: 30px;
    span {
      font-weight: 100;
    }
  }
}

.weather__icon {
  line-height: 79px;
  padding-right: 10px;
}

.weather__weekly {
  .weather__weekly__list {
    display: flex;
    list-style-type: none;
    display: flex;
    justify-content: space-around;
    li {
      p {
        text-align: center;
      }
      .weather__temp {
        margin-bottom: 10px;
        .weather__temp__high {
          font-weight: 50;
        }
        .weather__temp__low {
          font-weight: 100;
        }
      }
    }
  }
}

.weather__weekly__list__icon {
  font-size: 70px;
}

.weather__bottom {
  padding: 10px 0;
  border-top: 1px solid rgba(150, 113, 113, 0.5);
  a {
    color: rgba(233, 163, 163, 0.8);
    font-size: 12px;
    text-align: right;
  }
}

PickupArticle

PickupArticleコンポーネントの作成

ピックアップの記事を表示するコンポーネントを作成していきます。
キーワードで検索することで好きな記事をピックアップできます。

src/components/pickup-article/index.tsx
import styles from "./index.module.scss";
import moment from "moment";
import Props from "../types";

const PickupArticle: React.FC<Props> = ({ articles }) => {
  return (
    <section className={styles.pickup}>
      <h1 className={styles.article__heading}>PickUp</h1>
      {articles.map((article, index) => {
        const time = moment(article.publishedAt || moment.now())
            .fromNow()
            .slice(0, 1) == "a"
            ? 1
            : moment(article.publishedAt || moment.now())
                .fromNow()
                .slice(0, 1);
        return (
          <a href={article.url} key={index} target="_blank" rel="noopener">
            <article className={styles.article__main}>
              <div className={styles.article__title}>
                <p>{article.title}</p>
                <p className={styles.article__time}>
                  {time}時間前
                </p>
              </div>
              {article.urlToImage && (
                <img
                  key={index}
                  src={article.urlToImage}
                  className={styles.article__img}
                />
              )}
            </article>
          </a>
        );
      })}
    </section>
  );
};

export default PickupArticle;
src/components/pickup-article/index.module.scss
.pickup {
  border: 1.2px solid rgba(135, 135, 135, 0.5);
  border-radius: 15px;
  padding: 0 20px 0 20px;
  margin: 40px 20px 0 20px;
  h1 {
    font-weight: 1000;
    font-size: 16px;
    color: rgb(233, 163, 163);
    padding: 12px 0;
    border-bottom: 1px solid rgba(150, 113, 113, 0.5);
  }
}
.article__main {
  display: flex;
  margin-top: 20px;
  // padding: 15px;
  border-radius: 10px;
}
.article__title {
  flex: 4;
  margin-right: 5px;
  p {
    font-size: 13px;
  }
}

.article__time {
  opacity: 0.5;
}

.article__img {
  width: 80px;
  height: 80px;
  margin: auto;
  object-fit: cover;
}

PickupArticleコンポーネントの表示

src/pages/index.tsx
import Head from "next/head";
import MainLayout from "../layouts";
import styles from "../styles/Home.module.scss";
import Article from "../components/article";
import Nav from "../components/nav";
import WeatherNews from "../components/weather-news";
import PickupArticle from "../components/pickup-article";

export default function Home(props) {
  return (
    <MainLayout>
      <Head>
        <title>Simple News</title>
      </Head>
      <div className={styles.contents}>
        <div className={styles.nav}>
          <nav>
            <Nav />
          </nav>
        </div>
        <div className={styles.blank} />
        <div className={styles.main}>
          <Article title="headline" articles={props.topArticles} />
        </div>
        <div className={styles.aside}>
          <WeatherNews weatherNews={props.weatherNews} />
          <PickupArticle articles={props.pickupArticles} />
        </div>
      </div>
    </MainLayout>
  );
}

export const getStaticProps = async () => {
  // NewsAPIのトップ記事の情報を取得
  const pageSize = 10  //取得する記事の数
  const topRes = await fetch(
    `https://newsapi.org/v2/top-headlines?country=jp&pageSize=${pageSize}&apiKey=あなたのNewsAPIのAPIKey`
  );
  const topJson = await topRes.json();
  const topArticles = topJson?.articles;

  // OpenWeatherMapの天気の情報を取得
  const lat = 35.4122    // 取得したい地域の緯度と経度(今回は東京)
  const lon = 139.4130
  const exclude = "hourly,minutely"   // 取得しない情報(1時間ごとの天気情報と1分間ごとの天気情報)
  const weatherRes = await fetch(
    `https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&units=metric&exclude=${exclude}&appid=あなたのOpenWeatherMapのAPIKey`
  );
  const weatherJson = await weatherRes.json();
  const weatherNews = weatherJson;

  // NewsAPIのピックアップ記事の情報を取得
  const keyword = "software"   // キーワードで検索(ソフトウェア)
  const sortBy = "popularity"  // 表示順位(人気順)
  const pickupPageSize = 5     // ページサイズ(5)
  const pickupRes = await fetch(
    `https://newsapi.org/v2/everything?q=${keyword}&language=jp&sortBy=${sortBy}&pageSize=${pickupPageSize}&apiKey=あなたのNewsAPIのAPIKey`
  );
  const pickupJson = await pickupRes.json();
  const pickupArticles = pickupJson?.articles;

  return {
    props: {
      topArticles,
      weatherNews,
      pickupArticles
    },
    revalidate: 60,
  };
};

スクリーンショット 2021-02-10 12.21.43.png

これで完成です。

Vercelでデプロイ

Next.jsを作ったVercelでデプロイします。

今回はGithubアカウントでログインします。





これでデプロイができました。

まとめ

今回初めてQiitaで記事を書いて、誤字脱字や理解の浅い部分もあると思います。
初めてNext.jsを使って何かを作りたい人たちなどの役に立てれば幸いです。
最後までこの記事を読んで頂いて本当にありがとうございます。🙇‍♀️🙇‍♀️🙇‍♀️🙇‍♀️

ソースコードは、こちら

使用したものや参考にしたサイト
- NewsAPI
- OpenWeatherMap
- flaticon
- Google News


駆け出しの学生エンジニアです。
Twitterのフォローよろしくお願いします。

574
572
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
574
572