📝

microCMSで職務経歴書をつくってみた

2024/12/05に公開

こちらは「microCMSでこんなことができた!あなたのユースケースを大募集 by microCMS Advent Calendar 2024」6日目の記事です。

初めての転職、初めての職務経歴書

今年初めて転職をしたのですが、恥ずかしながらこれまで職務経歴書という存在をよく知りませんでした…!調べてみると、職種によってフォーマットが多岐にわたっていて、何をどんな順番でどれくらい書けばいいのかわからず、試行錯誤しました。エンジニアはテクニカルスキルを記述するケースもあると思うので、特殊ですよね。

特にしっくりこなかったのが、どのアプリケーションを使って書くかでした。普段個人的なドキュメント作成にはNotionを使用していますが、ここで気づいたのはNotionにはテキストの右揃えスタイル設定がないこと!(職務経歴書では冒頭の日付や締めの「以上」は右揃えがお決まりらしい)
ではGoogleドキュメントか…?と思って作り始めたけれど何だか筆が進まない。とはいえアプリ選定にあまり時間を割くわけにもいかないので、そのままGoogleドキュメントで書き上げてしまいました。

提出した後まず思い立ったのが、「CMSでコンテンツ管理できる職務経歴書をつくろう!」ということでした。せっかくの個人開発なので、フレームワークは今まで使用したことがなかったRemixを採用。CMSは柔軟にAPI設計ができるmicroCMSにしました。

microCMSのAPIスキーマ設定

以下のようにmicroCMSのAPIスキーマを設定していきます。
使用するコンテンツAPIは一点のみ(オブジェクト形式)です。

コンテンツ

エンドポイント: contents
APIの型: オブジェクト形式

フィールド ID 表示名 種類
lastUpdate 最終更新日 日時
name 氏名 テキストフィールド
occupation 職業 テキストフィールド
contacts リンクURL 繰り返しフィールド(contact)
profileImage プロフィール画像 画像
summary 職務要約 テキストエリア
skills 活かせる経験・知識・技術 繰り返しフィールド(skill)
careers 職務経歴 繰り返しフィールド(career)
selfPresentations 自己PR 繰り返しフィールド(selfPresentation)
backgroundColor 背景色 テキストフィールド
textColor 文字色 テキストフィールド

カスタムフィールド

カスタムフィールド名: 連絡先、SNSのURLなど
フィールドID: contact

フィールド ID 表示名 種類
name 文字列 テキストフィールド
url URL テキストフィールド


カスタムフィールド名: スキルセット
フィールドID: skill

フィールド ID 表示名 種類
name スキル・資格 テキストフィールド
years 経験年数 数字
level 熟練度・説明 テキストエリア


カスタムフィールド名: 会社の概要
フィールドID: career

フィールド ID 表示名 種類
company 会社名 テキストフィールド
period 在席期間 テキストフィールド
business 事業内容 テキストフィールド
stock 資本金 テキストフィールド
employees 従業員数 数字
projects 担当したプロジェクト 繰り返しフィールド(project)


カスタムフィールド名: プロジェクトについて
フィールドID: project

フィールド ID 表示名 種類
name プロジェクト名 テキストフィールド
period 期間 テキストフィールド
content プロジェクト概要や業務内容、実績、規模等 リッチエディタ


カスタムフィールド名: 自己PR
フィールドID: selfPresentation

フィールド ID 表示名 種類
headline 見出し テキストフィールド
content 内容 テキストエリア

参考にした記事

RemixとmicroCMSを連携させるために参考にしたのはこちら記事です。
https://dev.classmethod.jp/articles/remix-with-microcms/

実装

app/types/content.tsでコンテンツの型情報を定義します。

app/types/content.ts
import type {
  MicroCMSImage,
} from 'microcms-js-sdk';

export interface Content {
  name: string;
  occupation:string;
  contacts: contact[];
  backgroundColor:string;
  textColor:string;
  profileImage:MicroCMSImage;
  summary:string;
  lastUpdate:Date;
  skills:skill[];
  selfPresentations:selfPresentation[];
  careers:career[];
}

// カスタムフィールド > contactの型定義
export type contact = {
  id:string;
  fieldId: 'contact';
  name: string;
  url: string;
};

// カスタムフィールド > skillの型定義
export type skill = {
  id:string;
  fieldId: 'skill';
  name: string;
  years: number;
  level:string;
};

// カスタムフィールド > selfPresentationの型定義
export type selfPresentation = {
  id:string;
  fieldId: 'selfPresentation';
  headline: string;
  content:string;
};

// カスタムフィールド > careerの型定義
export type career = {
  id:string;
  fieldId: 'career';
  company: string;
  period:string;
  business:string;
  stock:string;
  employees:number;
  projects:project[];
};

// カスタムフィールド > projectの型定義
export type project = {
  id:string;
  fieldId: 'project';
  name: string;
  period:string;
  content:string;
};

app/routes/_index.tsxでコンテンツを読み込んで、表示させています。

app/routes/_index.tsx
import type { MetaFunction, LoaderFunction } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { client } from "~/libs/client.server";
import { formatDate } from "~/libs/util";
import type { Content } from "~/types/content";

export const meta: MetaFunction = ({data}) => {
  return [
    { title: "職務経歴書" },
    { name: "description", content: data.name + "の職務経歴書です" },
  ];
};

export const loader: LoaderFunction = async () => {
  const data = await client.get({ endpoint: "contents" });
  return data;
};

export default function Index() {
  const data = useLoaderData<Content>();
  return (
    <div className="min-h-screen p-10 font-MPLUS1p pt-16 pb-16">
      <div className="w-full max-w-4xl m-auto">
        <h1 className="text-center text-4xl mb-10 font-bold tracking-[.35em]">職務経歴書</h1>
        <h3 className="text-right mb-10">{formatDate(data.lastUpdate)}</h3>
        <div className="lg:flex mb-10">
          {data.profileImage &&
          <div className="lg:w-1/2 lg:mr-10 lg:mb-0 mb-6">
            <img src={data.profileImage.url} alt="" />
          </div>
          }
          <div className="lg:w-1/2 lg:flex lg:flex-col lg:justify-center">
            <h1 className="text-4xl font-bold mb-4">
              {data.name}
            </h1>
            <h2 className="text-2xl font-medium mb-4">
              {data.occupation}
            </h2>
            <ul className="">
            {data.contacts && data.contacts.map((contact,index) => (
              <li key={index} className="mb-2 text-lg">
                <a href={contact.url} target="_blank" rel="noreferrer">{contact.name}</a>
              </li>
            ))}
            </ul>
          </div>
        </div>
        <div className="leading-loose mb-14">
          <h3 className="text-2xl font-bold mb-4">職務要約</h3>
          {data.summary}
        </div>
        <div className="leading-loose mb-14">
          <h3 className="text-2xl font-bold mb-4">活かせる経験・知識・技術</h3>
          {data.skills && data.skills.map((skill,index) => (
            <div key={index} className="mb-6">
              <h4 className="text-lg font-medium mb-1">{skill.name}
              {skill.years &&
              <span>(経験年数:{skill.years}年)</span>
              }
              </h4>
              {skill.level}
            </div>
          ))}
        </div>
        <div className="leading-loose mb-20">
          <h3 className="text-2xl font-bold mb-4">職務経歴</h3>
          {data.careers && data.careers.map((career,index) => (
            <div key={index} className="mb-6">
              <h4 className="text-xl font-medium mb-1">{career.company}
              {career.period &&
              <span>(在席期間:{career.period}</span>
              }
              </h4>
              {career.business &&
              <h5>事業内容:{career.business}</h5>
              }
              <div className="flex mb-5">
                {career.stock &&
                <h5 className="mr-5">資本金:{career.stock}</h5>
                }
                {career.employees &&
                <h5>従業員数:{career.employees}</h5>
                }
              </div>
              {career.projects && career.projects.map((project,index) => (
                <div key={index} className="mb-12 p-6" style={{ border: "1px solid #" + data.textColor }}>
                  <h4 className="text-xl font-medium mb-1">{project.name}</h4>
                  {project.period &&
                    <h5 className="mb-6">期間:{project.period}</h5>
                  }
                  <div className="project-detail" dangerouslySetInnerHTML={{ __html: project.content }} />
                </div>
              ))}
            </div>
          ))}
        </div>
        <div className="leading-loose mb-20">
          <h3 className="text-2xl font-bold mb-4">自己PR</h3>
          {data.selfPresentations && data.selfPresentations.map((selfPresentation,index) => (
            <div key={index} className="mb-6">
              <h4 className="text-lg font-medium mb-1">{selfPresentation.headline}</h4>
              {selfPresentation.content}
            </div>
          ))}
        </div>
        <p className="text-right mb-12">以上</p>
      </div>
    </div>
  );
}

microCMS管理画面

職務要約、活かせる経験・知識・技術

各項目を設定可能
各項目を設定可能、個数も制限ありません

職務経歴


所属していた会社の概要、担当したプロジェクトが繰り返しフィールド

完成したページ

pdfに書き出せば提出も可能。
https://remix-microcms-resume-template.vercel.app

GitHubリポジトリ

https://github.com/nakanohiroko/remix-microcms-resume-template

つくってみて

自分の思う通りに余白や行間、文字サイズ、文字間、フォントなどを設定でき、更新も簡単で結構いいのでは?と思っていたところにこちらのpostが流れてきました。履歴書まで…!?
しかも、サイズ調整機能があったり、PreviewURLを使用することで提出先ごとに内容を変更できるとのことで、こういう部分まで気が効いてこそだな〜!と思いました。素晴らしい👏

【後日談】 転職活動はどうなった?

8月よりマーケターとしてmicroCMSにジョインしました🙌

microCMSでこんなことができた!あなたのユースケースを大募集 by microCMS Advent Calendar 2024」まだ参加可能ですので、ご興味ある方はぜひチェックしてみてください(プレゼント企画も行っております🎁)!

Discussion