Closed2

Next 14 App Router 使ってみた。

knaka Tech-Blogknaka Tech-Blog

概要

Next 14 App Router 使う例になります。

  • App Routerを選択する例です。
  • 更新系は、 api route 通信する形です。
  • テストは、vercelに設置する例です。

[ 公開: 2024/04/22]


環境

  • Next 14
  • vercel
  • Turso

関連


作成したコード

https://github.com/kuc-arc-f/nextjs14_2


ClientComponent

  • app/test2/page.tsx
  • ClientComponent, ServerComponent 1画面に実装できそうです。
page.tsx
import ClientCompo1 from "./ClientCompo1";
import ServerCompo1 from "./ServerCompo1";
//
export default async function Page() {
  return (
    <div>
      <h1>Test2 !!!</h1>
      <hr />
      <ServerCompo1 />
      <hr />
      <ClientCompo1 />
    </div>
  );
}

  • app/test2/ClientCompo1.tsx: リストの部分
  • 先頭に、use client 書くとclient実装できそうです。
  • 内部api (/app/api/xxxx/route.ts) と連携する例です。
  • この例は、D1 + CF-workesと連携します。
ClientCompo1.tsx
"use client";
import {useState, useEffect}  from 'react';
import React from 'react'
import HttpCommon from '../lib/HttpCommon';
import CrudIndex from './CrudIndex';

let pageItems: any[] = [];
//
export default function Compo() {
  console.log("Client Componentを実行しています");
  const [updatetime, setUpdatetime] = useState<string>("");
  //
  useEffect(() => {
    (async () => {
      setUpdatetime(new Date().toString() + String(Math.random()));
      getList();
    })()
  }, []);
  /**
   *
   * @param
   *
   * @return
   */
  const getList = async function(){
    try {
      const item  = {
        "userId": 0,
      }      
      const json = await HttpCommon.serverPost(item, "/test/get_list");
  console.log(json);
      pageItems = json.data;
        setUpdatetime(new Date().toString() + String(Math.random()));
    } catch (e) {
      console.error(e);
    } 
  }
  /**
   *
   * @param
   *
   * @return
   */
  const addPorc = async function(){
    try {
      await CrudIndex.addItem(); 
      location.reload();
    } catch (e) {
      console.error(e);
    } 
  }
  //
  return (
    <div >
      <h1>ClientCompo1.tsx</h1>
      <hr />
      <label>Title:</label>
      <input type="text" id="title" name="title"
      className="border border-gray-400 rounded-md px-3 py-2 w-full focus:outline-none focus:border-blue-500"
      /> 
      <hr className="my-1" />
      <label>Content:</label>
      <input type="text" id="content" name="content"
      className="border border-gray-400 rounded-md px-3 py-2 w-full focus:outline-none focus:border-blue-500"
      /> 
      <hr className="my-1" />
      <button onClick={()=>addPorc()}>Add</button>
      <hr className="my-1" />
      {pageItems.map((item: any ,index: number) => {
      return (
      <div key={index}>
          <h3 className="text-3xl font-bold">{item.title}</h3>
          <span>ID: {item.id}, {item.createdAt}</span>
          <a href={`/testshow?id=${item.id}`}>[ Show ]</a>
          <hr />
      </div>
      )
      })} 
    </div>
  );
}


  • app/api/send_post/route.ts: 内部API (中継する中間API)
route.ts

import { NextResponse } from "next/server";

//
export async function POST(req: Request) {
  try{  
    const reqJson = await req.json();
      const url = process.env.API_URI; 
      const path = reqJson.api_url;	
console.log("path=", url + path);
      const body: any = JSON.stringify(reqJson);		
      const res = await fetch(url + path, {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},      
        body: body
      });
      const json = await res.json()
    return NextResponse.json(json);
  } catch (error) {
    console.error(error);
    return NextResponse.json({ret: 'NG', msg: 'sendPost_msg'});
  }
}


  • package.json
{
  "name": "nextjs14_2",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "react": "^18",
    "react-dom": "^18",
    "next": "14.2.2"
  },
  "devDependencies": {
    "typescript": "^5",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18"
  }
}


knaka Tech-Blogknaka Tech-Blog

Turso LibSQL 使うサンプル

https://github.com/kuc-arc-f/nextjs14_2/tree/main/sample/turso_test


API

  • app/api/turso_get_list/route.ts
  • api routeから、直接DB連携します。
route.ts
import { NextResponse } from "next/server";
import LibTurso  from '../lib/LibTurso';

//
export async function POST(req: Request) {
  const retObj = {ret: "NG", data: [], message: ""};
  try{  
    const reqJson = await req.json();
      const client = await LibTurso.getClient();
      const sql = `SELECT * FROM todos ORDER BY id DESC LIMIT 100;`;
console.log(sql);
      const resulte = await client.execute(sql);
      retObj.ret = "OK";
      //@ts-ignore
      retObj.data = resulte.rows;
    return NextResponse.json(retObj);
  } catch (error) {
    console.error(error);
    return NextResponse.json(retObj);
  }
}

  • ClientComponent

  • app/api/turso_test/ClientCompo1.tsx

ClientCompo1.tsx
"use client";
import {useState, useEffect}  from 'react';
import React from 'react'
import Link from 'next/link'

import HttpCommon from '../lib/HttpCommon';
import CrudIndex from './CrudIndex';

let pageItems: any[] = [];
//
export default function Compo() {
  console.log("Client Componentを実行しています");
  const [updatetime, setUpdatetime] = useState<string>("");
  //
  useEffect(() => {
    (async () => {
      setUpdatetime(new Date().toString() + String(Math.random()));
      getList();
    })()
  }, []);
  /**
   *
   * @param
   *
   * @return
   */
  const getList = async function(){
    try {
      const json = await CrudIndex.getList()
console.log(json);
      pageItems = json.data;
        setUpdatetime(new Date().toString() + String(Math.random()));
    } catch (e) {
      console.error(e);
    } 
  }
  /**
   *
   * @param
   *
   * @return
   */
  const addPorc = async function(){
    try {
      await CrudIndex.addItem(); 
      location.reload();
    } catch (e) {
      console.error(e);
    } 
  }
  //
  return (
    <div >
      <h1>ClientCompo1.tsx</h1>
      <hr />
      <label>Title:</label>
      <input type="text" id="title" name="title"
      className="border border-gray-400 rounded-md px-3 py-2 w-full focus:outline-none focus:border-blue-500"
      /> 
      <hr className="my-1" />
      <label>Content:</label>
      <input type="text" id="content" name="content"
      className="border border-gray-400 rounded-md px-3 py-2 w-full focus:outline-none focus:border-blue-500"
      /> 
      {/*
      <button onClick={()=>addPorc()}>Add</button> 
      <hr className="my-1" />
      */}
      <hr className="my-1" />
      {pageItems.map((item: any ,index: number) => {
      return (
      <div key={index}>
          <h3 className="text-3xl font-bold">{item.title}</h3>
          <span>ID: {item.id}, {item.createdAt}</span>
          <hr />
      </div>
      )
      })} 
    </div>
  );
}


  • package.json
{
  "name": "nextjs14_2",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@libsql/client": "^0.6.0",
    "next": "14.2.2",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "typescript": "^5"
  }
}

このスクラップは2024/04/26にクローズされました