脱Excel！管理しやすいドキュメントツールを作ってみた件


# ドキュメント管理、だるくないっすか？😅

おはこんばんちは。
ドキュメント管理、非常に面倒ですよね。
何が面倒かってお客様との打ち合わせが重なった際に変更履歴が追えないのはきついっすよね。

例えば…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2028663/0bdfc344-8c41-46d6-9d9b-54d7ab567748.png)

ChatGPTくんも詳しくて例が出てきましたよ。闇深いっすね

こんな内容を解決していくためのツールを作りました。


## 深淵を覗かなければ、深淵はこちらを覗かない👿

以下のような形で構成してみました。

- Server
    - Vercel
- 言語
    - Next.js (React.js)
- ドキュメント記載ツール
    - Markdown
- 共通仕様
    - プロジェクト毎に簡単にサイトを作成できるようにしたい
    - 外出先でもレスポンシブ対応にしたい
    - ページの作成はMarkdownで作成できるようにしたい
    - Basic認証を使って簡易的にでも閲覧者を限定したい
    - Markdownファイルは/docsに格納していく

こうすれば深淵はこちらを覗かないように出来るか？と検討

## Let's Coding!💻

こんな感じでやっていきます


```tsx:[slug].tsx
import { getFileContent, getFileName } from "@/lib/fileUtil";
import { toHtml } from "@/lib/markdownUtil";
import { GetServerSidePropsContext } from "next";
import { NextRequest, NextResponse } from "next/server";
import { useEffect } from "react";

export default function Content({ content }: { content: string }) {

  return (
    <div className="content" dangerouslySetInnerHTML={{
      __html: content,
    }}></div>
  )
}


export const getServerSideProps = async (context: GetServerSidePropsContext) => {
  const query = decodeURI(context.query.slug as string);
  const fileNames = getFileName();

  if (query.match(/.md/)) {
    return {
      props: {
        fileNames,
        isError: true,
        title: query,
        content: null,
      }
    }
  }
  const markdownFile = getFileContent(query);
  if (markdownFile == null) {
    return {
      props: {
        fileNames,
        isError: true,
        title: query,
      }
    }
  }
  const content = await toHtml(markdownFile);
  return {
    props: {
      fileNames,
      isError: false,
      title: query,
      content: content,
    },
  }
};
```

MarkdownファイルをMarked.jsで変換して、HTML側に描画していきます。
例えば`/要件定義書`にアクセスされた場合は/docs/要件定義書.mdへアクセスするようにします。

```tsx:_app.tsx

interface PageProps {
  fileNames: string[];
  isError?: boolean,
  title?: string,
  content?: string,
}


const App = ({ Component, pageProps: _PageProps }: AppProps) => {
  const {
    fileNames,
    isError,
    title,
  } = _PageProps as PageProps;

  const titleTag = `${process.env.NEXT_PUBLIC_PROJECT_NAME} | ${title || 'Home'}` ;

  return (
    <>
      <Head>
        <title>{titleTag}</title>
      </Head>
      <Navbar
        pageTitle="Home"
        navbarStart={(fileNames || []).map(x => ({
          show: true,
          type: 'LinkItem',
          label: x,
          pathname: x,
        }))}
      />
      <div className="container">
        <section className="section">
          <div className="hero">
            {
              !isError ? (
                <Component {..._PageProps}/>
              ) : <Error />
            }
          </div>
        </section>
      </div>
    </>
  );
}

export default App;
```
Navbarの項目はMarkdownのファイル名で作成されるようにしていきます。

### 画像について📷
Vercelは画像関連で読み込みが遅いので、API側で読み込みをしても504エラーが出てしまいます。
（ここら辺原因を調べてみても不明でした😫）
しかし画像はアップロードしたいですよね。
なので以下のようにしていきます。

```json:package.json
  "scripts": {
    "dev": "next dev",
    "build": "yarn run cp && next build",
    "start": "next start",
    "lint": "next lint",
    "cp:pngImg": "cpx \"docs/img/*.png\" public/img/",
    "cp:jpegImg": "cpx \"docs/img/*.jpeg\" public/img/",
    "cp:png": "cpx \"docs/*.png\" public/",
    "cp:jpeg": "cpx \"docs/*.jpeg\" public/",
    "cp": "yarn run cp:pngImg && yarn run cp:jpegImg && yarn run cp:png && yarn run cp:jpeg"
  }
  
```

こんな感じでコピーしていきますよ。
デプロイの際に/docsに配置している画像をpublicにコピーしていくとWebアプリ上でも閲覧ができるようになります。


## 完成した画面をみてみよう 🥰


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2028663/342e3120-709e-4e0c-8115-ec2406e10d1b.png)

こんな感じのファイルが

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2028663/1bf0803f-2efd-4ed7-90f8-ba70fd880381.png)

こんな感じで表示されます。

簡単だけど便利だねー。
ぜひ作ってみてね！！

***
オブジェクティブグループではXの投稿も平日毎日行っています！
IT関連の小ネタや便利技から、日常のアニメ・ゲーム布教なども幅広く投稿してるので、
ご興味のある方は是非フォロー・いいねをお願いします。
https://x.com/obg_ocr