# Using Collected Notes as CMS

Now that Collected Notes has an API to get the notes you wrote here it's possible to use it as a CMS (Content Management System) for a custom website or blog. And it's actually pretty simple! Let's build one using Next.js for our Stack.

## Create Next Project

First, let's create the Next.js app.

```sh
# Init project
$ yarn init --yes
# Install dependencies
$ yarn add next react react-dom marked swr
# Install development dependencies
$ yarn add -D @types/node @types/react typescript
# Create gitignore
$ npx gitignore node
```

And let's add these scripts to the package.json `scripts` key.

```json
{
  "dev": "next",
  "build": "next build",
  "start": "next start"
}
```

## Create Data Fetching Helpers

To make our code a little bit easy to follow, let's create a two files `src/data/notes.ts` and `src/data/site.ts` with the functions to fetch the Collected Notes API and the interface of the returned data.

```ts
// src/data/notes.ts
export interface Note {
  id: number;
  site_id: number;
  user_id: number;
  body: string;
  path: string;
  headline: string;
  title: string;
  created_at: string;
  updated_at: string;
  visibility: string;
  url: string;
}

export function readNote(site: string, path: string): Promise<Note> {
  return fetch(`https://collectednotes.com/${site}/${path}.json`).then((res) =>
    res.json()
  );
}
```

```ts
// src/data/site.ts
import useSWR, { ConfigInterface } from "swr";
import { Note } from "./notes";

export interface Site {
  site: {
    id: number;
    user_id: number;
    name: string;
    headline: string;
    about: string;
    host: any;
    created_at: string;
    updated_at: string;
    site_path: string;
    published: boolean;
    tinyletter: string;
    domain: string;
  };
  notes: Note[];
}

export function readSite(site: string): Promise<Site> {
  return fetch(`https://collectednotes.com/${site}.json`).then((res) =>
    res.json()
  );
}

export function useSite(site: string, options: ConfigInterface) {
  return useSWR<Site, never>(site, readSite, options);
}
```

Our `readSite` and `readNote` functions receive the site from where we want to read our notes and the path of the note.

There is also a `useSite` custom React hook to let us fetch the site data client-side, we will come back to this later.

## Create First Page

With our data fetching modules ready, we can work on the code of the pages, let's create the first page on `src/pages/index.tsx`, this will be the content:

```tsx
import { GetStaticProps, InferGetStaticPropsType } from "next";
import Link from "next/link";
import { Note } from "data/notes";
import { readSite, Site, useSite } from "data/site";

// This function will fetch the list of notes and the site data at build time
export const getStaticProps: GetStaticProps<Site> = async () => {
  const { site, notes } = await readSite(process.env.SITE_PATH);

  return { props: { site, notes } };
};

// This is the list of notes component, it will receive the list of notes, and
// site data, getStaticProps fetch at build time as props
export default function Notes(
  props: InferGetStaticPropsType<typeof getStaticProps>
) {
  // we use the props as initial value for useSite so in case we add a new note
  // this will ensure we revalidate against the API and get the updated list
  // without deploying again 🤯
  const { data } = useSite(process.env.SITE_PATH, {
    initialData: props,
    revalidateOnMount: true,
  });

  return (
    <section>
      <header>
        <h1>{data.site.name}</h1>
      </header>
      <ul>
        {data.notes.map((note) => (
          <li key={note.id}>
            <Link href="/notes/[slug]" as={`/notes/${note.path}`}>
              <a>
                <article>
                  <h2>{note.title}</h2>
                  <p>{note.headline}</p>
                </article>
              </a>
            </Link>
          </li>
        ))}
      </ul>
    </section>
  );
}
```

## Create Note Page

And now, we can create the page to render a single note, this will be created as `src/pages/notes/[slug].tsx`

```tsx
import {
  GetStaticProps,
  GetStaticPropsContext,
  InferGetStaticPropsType,
  GetStaticPaths,
} from "next";
import { useRouter } from "next/router";
import marked from "marked";
import { readNote, Note } from "data/notes";
import { readSite, Site } from "data/site";
import Link from "next/link";

function unwrap<Value>(value: Value | Value[]): Value {
  if (Array.isArray(value)) return value[0];
  return value;
}

// Here we fetch the site data to get the full list of notes, this let us know
// what notes we have at build time, we also enable the fallback, this makes
// Next.js render our component below in fallback mode while fetching the
// data of the note, in case it doesn't exist yet
export const getStaticPaths: GetStaticPaths = async () => {
  const { notes } = await readSite(process.env.SITE_PATH);
  const paths = notes.map((note) => {
    return { params: { slug: note.path } };
  });
  return { paths, fallback: true };
};

// Here we fetch the individual note data, and the site data, at build time
export const getStaticProps: GetStaticProps<{
  note: Note;
  site: Site["site"];
}> = async ({ params }: GetStaticPropsContext) => {
  const { slug } = params;
  const { site } = await readSite(process.env.SITE_PATH);
  const note = await readNote(process.env.SITE_PATH, unwrap<string>(slug));
  return { props: { note, site } };
};

// This is our note component, it receive the site data and the note as props
export default function Note(
  props: InferGetStaticPropsType<typeof getStaticProps>
) {
  // here we detect if the page should render as a fallback, below we will use
  // this `isFallback` to render a `Loading {something}...` message or the real
  // data
  const { isFallback } = useRouter();
  return (
    <section>
      <header>
        <h2>
          <Link href="/">
            <a>{isFallback ? "Loading site title..." : props.site.name}</a>
          </Link>
        </h2>
      </header>
      <article
        dangerouslySetInnerHTML={{
          __html: isFallback
            ? "<h1>Loading note title...</h1><p>Loading note body...</p>"
            : marked(props.note.body),
        }}
      />
      <footer>
        <p>
          {isFallback ? (
            "Loading note metadata..."
          ) : (
            <>
              Updated on{" "}
              <time dateTime={props.note.updated_at}>
                {new Intl.DateTimeFormat("en-US", {
                  weekday: "long",
                  year: "numeric",
                  month: "long",
                  day: "numeric",
                }).format(new Date(props.note.updated_at))}
              </time>
            </>
          )}
        </p>
      </footer>
    </section>
  );
}
```

## Deploying

Lastly, we need to deploy it, we can do it easily with [Vercel](https://vercel.com), create an account if you don't have one, and push your blog code to GitHub, then import it from the Vercel Dashboard, you can also deploy the repo I already have prepared with one-click

[![](https://vercel.com/button)](https://zeit.co/new/project?template=https://github.com/sergiodxa/collected-notes-next-blog)

You will need to add a `SITE_PATH` environment variable to your website to customize the website path you are using, if your Collected Notes website is `https://collectednotes.com/esacrosa` then `esacrosa` is your `SITE_PATH`.

### Styles

While the example code doesn't have any style, the website deployed with the button above have styles, I used [Tailwind CSS](https://tailwindcss.com) for that, you can check the code in https://github.com/sergiodxa/collected-notes-next-blog.