はじめてのPlasmic


## はじめに

PlasmicというヘッドレスCMSがあるらしい、
という噂を聞いて、何も前提知識は無いですが触ってみた際の備忘録です。

## Plasmicの利用準備

### アカウント作成

[Plasmicのサイト上](https://www.plasmic.app/cms)でアカウントを作成します

![スクリーンショット 2023-02-26 16.27.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395295/6975aa27-2db1-3e11-3bc5-053d73725cd0.png)

![スクリーンショット 2023-02-26 16.27.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395295/c9a86643-ed47-a333-42c1-c647dac3b909.png)

\作成完了🥳/

![スクリーンショット 2023-02-26 16.27.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395295/578c0a72-7d4a-367a-0997-7570ef710a0b.png)

### Plasmicでのプロジェクト作成

この後、フロントと繋げるための適当なプロジェクトを一つ作っておきます

![スクリーンショット 2023-02-26 16.27.42 4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395295/1669dcde-b6d7-0566-8e8c-0435878827a9.png)

今回はテンプレートを利用してみます

![スクリーンショット 2023-02-26 16.57.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395295/70f991f1-c9ce-f809-c123-a5275b42feba.png)


![スクリーンショット 2023-02-26 16.58.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395295/e144d33d-c704-bd73-c404-55a23f15911e.png)

\作成完了🥳/

画面右上の`Code`を押すと、作成したプロジェクトを埋め込むための
サンプルコードが認証トークンを入れた状態で確認できます

![スクリーンショット 2023-02-26 17.03.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395295/01dfc786-cb46-f38a-1554-7c8debea4d90.png)


## 早速使ってみる

### 今回の利用環境


```sh
> node -v
v16.17.0
> yarn -v
1.22.17
```

```json
{
  "@plasmicapp/loader-nextjs": "^1.0.232",
  "next": "13.2.1"
}

```

## Get started with Next.js

今回は、Next.jsを利用したハンズオンの中の

- 記事を表示するまで(Auto load all Plasmic pages)
- カスタムコンポーエントの追加（Adding custom code components）

をやっていきます


https://docs.plasmic.app/learn/nextjs-quickstart

### setup Next.js

とりあえず、Next.jsのアプリケーションを作成


```sh
yarn create next-app
✔ What is your project named? … plasmic-sample
✔ Would you like to use TypeScript with this project? … No / Yes
✔ Would you like to use ESLint with this project? … No / Yes
✔ Would you like to use `src/` directory with this project? … No / Yes
✔ Would you like to use experimental `app/` directory with this project? … No / Yes
✔ What import alias would you like configured? … @/*
```

\作成完了🥳/


### install plasmicapp

Plasmicのパッケージを入れます

```sh
yarn add @plasmicapp/loader-nextjs
```

### create plasmic-init.ts

```
touch 'src/plasmic-init.ts'
```

<details>
<summary>サンプルコードをコピペ</summary>

```ts:src/plasmic-init.ts
import { initPlasmicLoader } from "@plasmicapp/loader-nextjs";
export const PLASMIC = initPlasmicLoader({
  projects: [
    {
      id: "PROJECTID",  // ID of a project you are using
      token: "APITOKEN"  // API token for that project
    }
  ],
  // Fetches the latest revisions, whether or not they were unpublished!
  // Disable for production to ensure you render only published changes.
  preview: true,
})
```

</details>


### Auto load all Plasmic pages

```
touch 'src/pages/[[...catchall]].tsx'
```

<details>
<summary>サンプルコードをコピペ</summary>

```ts:src/pages/[[...catchall]].tsx
import * as React from 'react';
import {
  PlasmicComponent,
  ComponentRenderData,
  PlasmicRootProvider,
  extractPlasmicQueryData
} from '@plasmicapp/loader-nextjs';
import { GetStaticPaths, GetStaticProps } from 'next';
import Error from 'next/error';
import { useRouter } from 'next/router';
import { PLASMIC } from '../plasmic-init';

/**
 * Use fetchPages() to fetch list of pages that have been created in Plasmic
 */
export const getStaticPaths: GetStaticPaths = async () => {
  const pages = await PLASMIC.fetchPages();
  return {
    paths: pages.map((page) => ({
      params: { catchall: page.path.substring(1).split('/') }
    })),
    fallback: 'blocking'
  };
};

/**
 * For each page, pre-fetch the data we need to render it
 */
export const getStaticProps: GetStaticProps = async (context) => {
  const { catchall } = context.params ?? {};

  // Convert the catchall param into a path string
  const plasmicPath =
    typeof catchall === 'string' ? catchall : Array.isArray(catchall) ? `/${catchall.join('/')}` : '/';
  const plasmicData = await PLASMIC.maybeFetchComponentData(plasmicPath);
  if (!plasmicData) {
    // This is some non-Plasmic catch-all page
    return {
      props: {}
    };
  }

  // This is a path that Plasmic knows about.
  const pageMeta = plasmicData.entryCompMetas[0];

  // Cache the necessary data fetched for the page.
  const queryCache = await extractPlasmicQueryData(
    <PlasmicRootProvider loader={PLASMIC} prefetchedData={plasmicData} pageParams={pageMeta.params}>
      <PlasmicComponent component={pageMeta.displayName} />
    </PlasmicRootProvider>
  );

  // Pass the data in as props.
  return {
    props: { plasmicData, queryCache },

    // Using incremental static regeneration, will invalidate this page
    // after 300s (no deploy webhooks needed)
    revalidate: 300
  };
};

/**
 * Actually render the page!
 */
export default function CatchallPage(props: { plasmicData?: ComponentRenderData; queryCache?: Record<string, any> }) {
  const { plasmicData, queryCache } = props;
  const router = useRouter();
  if (!plasmicData || plasmicData.entryCompMetas.length === 0) {
    return <Error statusCode={404} />;
  }
  const pageMeta = plasmicData.entryCompMetas[0];
  return (
    // Pass in the data fetched in getStaticProps as prefetchedData
    <PlasmicRootProvider
      loader={PLASMIC}
      prefetchedData={plasmicData}
      prefetchedQueryData={queryCache}
      pageParams={pageMeta.params}
      pageQuery={router.query}
    >
      {
        // pageMeta.displayName contains the name of the component you fetched.
      }
      <PlasmicComponent component={pageMeta.displayName} />
    </PlasmicRootProvider>
  );
}
```

</details>



※Next.jsアプリ作成時に自動生成される`src/pages/index.tsx`が残っているとルーティングが被ってエラーが出るので注意
（`Error: You cannot define a route with the same specificity as a optional catch-all route`）

```sh
yarn dev
open http://localhost:3000
```

\作成完了🥳/

![スクリーンショット 2023-02-26 17.19.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395295/2fc7e1a5-66e5-6fd0-1764-35db5b6941fa.png)


### Adding custom code components

#### stap.1

```
mkdir 'src/components'    
touch 'src/components/HelloWorld.tsx'
```

<details>
<summary>サンプルコードをコピペ</summary>

```ts:src/components/HelloWorld.tsx
import * as React from 'react';

export interface HelloWorldProps {
  children?: React.ReactNode;
  className?: string;
  verbose?: boolean;
}

export function HelloWorld({ children, className, verbose }: HelloWorldProps) {
  return (
    <div className={className} style={{ padding: '20px' }}>
      <p>Hello there! {verbose && 'Really nice to meet you!'}</p>
      <div>{children}</div>
    </div>
  );
}
```

</details>

#### step.2

`plasmic-init.ts`を修正

```ts:src/plasmic-init.ts
import { initPlasmicLoader } from "@plasmicapp/loader-nextjs";
import { HelloWorld } from './components/HelloWorld'; // 追加

export const PLASMIC = initPlasmicLoader({
  projects: [
    {
      id: "PROJECTID",  // ID of a project you are using
      token: "APITOKEN"  // API token for that project
    }
  ],
  // Fetches the latest revisions, whether or not they were unpublished!
  // Disable for production to ensure you render only published changes.
  preview: true,
})

// 追加
PLASMIC.registerComponent(HelloWorld, {
  name: 'HelloWorld',
  props: {
    verbose: 'boolean',
    children: 'slot'
  }
});
```

#### step.3

```
touch 'src/pages/plasmic-host.tsx'
```

<details>
<summary>サンプルコードをコピペ</summary>

```ts:src/pages/plasmic-host.tsx
import * as React from 'react';
import { PlasmicCanvasHost } from '@plasmicapp/loader-nextjs';
import { PLASMIC } from '../plasmic-init';

export default function PlasmicHost() {
  return PLASMIC && <PlasmicCanvasHost />;
}
```

</details>


#### 確認

```
yarn dev
open http://localhost:3000/plasmic-host
```

(確認メッセージが表示されるらしいけど、うまく表示されなかった..けど
カスタムコンポーネント自体は正常に追加されいたので...ヨシッ!!)

※この後、Plasmic側の設定で利用するので`dev`環境は立ち上げ続けておいてください

#### Plasmic側で設定

[Plasmicのサイト上](https://studio.plasmic.app)にもどり、
カスタムコンポーネントを取得するページを設定すると、
CMSのGUI上で扱えるようになります

![スクリーンショット 2023-02-26 18.19.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395295/9ce375c1-76e8-1566-f374-f1abd31403e0.png)

\作成完了🥳/

![スクリーンショット 2023-02-26 18.17.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395295/bf4599a9-9181-0f36-ccc0-c1d8d0d1b0a6.png)



## 最後に

お疲れ様でした！！🎉

特にカスタムコンポーネントをCMS側でGUIとして
利用できるのは楽しそうですね。

ショートコードをマーケやUIデザイナーが
より直感的に利用できそうです。

一方で、コンポーネントの理解が浅い
非開発のメンバーが触るには少し自由度が高すぎる印象も受けました。

そういったメンバーごとの権限設定や
すでに利用されているカスタムコンポーネトを変更した場合の
管理体制なども調査/考慮して運用にのるかは検討したいところです。
また、世の中にまだノウハウがなさそうなのでみんなで貯めていきましょう。

少しでもこの記事が誰かの参考になれば幸いです。