DEV Community

Cover image for I create my own homepage!
Rintaro Itokawa
Rintaro Itokawa

Posted on

I create my own homepage!

TL;DR

Rebuilt portfolio.

  1. High Speed
  2. Scalable
  3. State-of-the-art technology

https://re-taro.dev <- This is the Portfolio I build!

https://github.com/re-taro/re-taro.dev <- And this is the repository!

High Speed

We are using Next.js as our framework to do SG.

It's frustrating to be Vercel's dog, but I'll put up with it. It is because it is attractive to be able to optimize font and images easily.

Although it is called SG, JS runs at each page transition, so the movement is smooth (in other words, it is not pure).

Cloudinary is used for image management to deliver optimized images.

The size of the images is directly related to the display speed, so it is very helpful.

Scalable

Throughout

The component design is based on Atomic Design, a practice known as Component Driven Development, so it is easy to reuse and extend.

Web Page

In order to keep the information as up-to-date as possible and not to leave direct commits to this repository when changing information, an API server was built and information was exchanged through it.

Blog Page

Markdown (content management) + tsx (template engine); Ramanujan said that Markdown would not go out of date so easily and that he felt comfortable throwing it into another service someday (I don't know, but...).

The Markdown processor used unified assets.

It was fun because it was built using a combination of various unified APIs.

State-of-the-art technology

I like it. Yes, I like state-of-the-art technology. Below is an excerpt from my club's LT talk, listing the technologies used in this portfolio.

ナウくね?

Front-end

Next.js for framework, Tailwind CSS for styling (but I wanted to use CSS in JS, so I used twin.macro this time).

I'm using Urql as my GraphQL client.

Hosting is by Vercel (I'm a dog, woof woof).

Back-end

I'm using Juniper. You may have noticed that I am trying to write a GraphQL server in Rust.

It's difficult again. I want to escape to Nest.js right away.

I'm leaving it to render to deploy to (Heroku? That guy is...). I'm still trying to figure out what to do with GraphQL.

GraphQL is a justice, I thought while making it.

Others

I'm trying to use umami this time for analytics. The UI is really cute and I love it.

Feature

In the following, we will describe the specific functions we were able to implement and the libraries we used.

Tailwind CSS & twin.macro

I don't think Tailwind CSS is justice. However, I think it is one of the solutions.

Also, since I adopted Atomic Design, I thought that developing with CSS in JS was the best choice, so I used twin.macro.

There was one major problem with twin.macro. It does not support Tailwind CSS v3.

The issue is still standing and is being monitored.

The flex-basis and other features added since Tailwind CSS v3 are naturally not supported, so the following action was taken.

ゴリ押しは正義

Urql

What do you think of GraphQL client libraries? Some well-known ones are Apollo and Relay. There is a library called @next/bundle-analyzer. Let's build it and look at the bundle size.

You will be surprised at the size of Apollo and Relay. You will be surprised at the size of Apollo and Relay, and how small Urql is in comparison. If you're curious, Urql does a comparison here.

Of course, it is the right size for the right place. In this case, I only needed a query, so I made it as small as possible.

/* pages/index.tsx */

// 略

// eslint-disable-next-line unicorn/prevent-abbreviations
export const getStaticProps: GetStaticProps<{ meta: SeoProperties; urqlState: SSRData }> = async () => {
  const client = await urqlClient();
  await client.query(HomeDocument).toPromise();
  const meta: SeoProperties = {
    description: "Rintaro Itokawa's Dev Site | re-taro",
    ogImageUrl: encodeURI(`${OGP_HOST}/api/ogp?title=re-taro`),
    pageRelPath: "",
    pagetype: "website",
    sitename: "re-taro.dev",
    title: "Rintaro Itokawa - Emotion Seeker",
    twcardtype: "summary_large_image",
  };
  return {
    props: {
      meta,
      urqlState: ssrCache.extractData(),
    },
  };
};

const HomePage: NextPage<Properties> = ({ meta }) => {
  const [response] = useQuery<HomeQuery>({ query: HomeDocument });
  return <Home data={response.data} meta={meta} />;
};

export default withUrqlClient(
  () => ({
    url: END_POINT,
  }),
  { neverSuspend: true, ssr: false },
)(HomePage);
Enter fullscreen mode Exit fullscreen mode

It was very simple and easy to use, although a little unusual to use.

GitHub Flavored Markdown

Supported by remark-gfm.

| Create     | Table    |
| -------- | ---------- |
| For | Example |
| increase   | Element.   |

- I can write
  - list. Also, [^1]

[^1]: Footnotes can be used.
Enter fullscreen mode Exit fullscreen mode
Create Table
For Example
increase Element.
  • I can write
    • list. Also, 1

Emoji

Convert with remakr-gemoji.

:v: convert to ✌️.

Numerical Formula

Bite the remark-math and rehype-katex.

It is easy to set up just by loading the style sheet.

// components/organisms/post-meta/index.tsx

// 略

const PostMeta: React.FC<PostMetaPropeties> = ({ meta }) => (
  <React.Fragment>
    <Seo {...meta} />
    <Head>
      <link
        rel={"stylesheet"}
        href={"https://cdn.jsdelivr.net/npm/katex@0.15.6/dist/katex.min.css"}
        integrity={"sha384-ljao5I1l+8KYFXG7LNEA7DyaFvuvSCmedUf6Y6JI7LJqiu8q5dEivP2nDdFH31V4"}
        crossOrigin={"anonymous"}
      />
      <title></title>
    </Head>
  </React.Fragment>
);

// 略
Enter fullscreen mode Exit fullscreen mode

ruby

I used remark-jaruby created by @haxibami.

The chain of methods around remark-parse / remark-rehype is as follows...

// utils/parser.ts

import rehypeShiki from "@re-taro/rehype-shiki";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeKatex from "rehype-katex";
import rehypeSlug from "rehype-slug";
import rehypeStringify from "rehype-stringify";
import remarkGemoji from "remark-gemoji";
import remarkGfm from "remark-gfm";
import remarkJaruby from "remark-jaruby";
import remarkMath from "remark-math";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import remarkToc from "remark-toc";
import remarkUnwrapImages from "remark-unwrap-images";
import * as shiki from "shiki";
import stripMarkdown from "strip-markdown";
import { unified } from "unified";

const MdToHtml = async (md: string) => {
  const result = await unified()
    .use(remarkParse)
    .use(remarkGfm)
    .use(remarkGemoji)
    .use(remarkMath)
    .use(remarkJaruby)
    .use(remarkUnwrapImages)
    .use(remarkToc, {
      heading: "目次",
      tight: true,
    })
    .use(remarkRehype)
    .use(rehypeKatex)
    .use(rehypeShiki, {
      highlighter: await shiki.getHighlighter({ theme: "nord" }),
    })
    .use(rehypeSlug)
    .use(rehypeAutolinkHeadings, {
      behavior: "wrap",
    })
    .use(rehypeStringify)
    .process(md);
  return result.toString();
};

export { MdToHtml }
Enter fullscreen mode Exit fullscreen mode

In addition, the rehype-react related process is as follows.

// lib/rehype-react.ts

import React from "react";
import rehypeParse from "rehype-parse";
import rehypeReact from "rehype-react";
import type { Options as RehypeReactOptions } from "rehype-react";
import { unified } from "unified";
import { Image } from "~/components/molecules/image";
import type { ImageProperties } from "~/components/molecules/image";
import { Link } from "~/components/molecules/link";
import type { LinkProperties } from "~/components/molecules/link";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const RehypeReact = (html: string): React.ReactElement<unknown, string | React.JSXElementConstructor<any>> => {
  const result = unified()
    .use(rehypeParse, {
      fragment: true,
    })
    .use(rehypeReact, {
      components: {
        // eslint-disable-next-line id-length
        a: (properties: LinkProperties) => Link(properties),
        img: (properties: ImageProperties) => Image(properties),
      },
      createElement: React.createElement,
    } as RehypeReactOptions)
    .processSync(html);
  return result.result;
};

export { RehypeReact };
Enter fullscreen mode Exit fullscreen mode

The above made the blog very easy to write.

dynamic OGP

I deployed to Vercel while looking at @haxibami's implementation.

https://github.com/re-taro/ogp.re-taro.dev <- This is the repository!

OGP

CI/CD

When the repository where the data is stored is updated, actions fires and sends a dispatch event to the portfolio repository, and the one that receives the dispatch event automatically builds the data. I like it, it's quite stylish.

Dispatcher

name: Dispatch
on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: |
          curl -vv -H "Authorization: token ${{ secrets.DISPATCH_TOKEN }}" -H "Accept: application/vnd.github.everest-preview+json" "https://api.github.com/repos/re-taro/re-taro.dev/dispatches" -d '{"event_type": "update"}'
Enter fullscreen mode Exit fullscreen mode

Receiver of the dispatch event

name: Dispatch production build

on:
  repository_dispatch:
    types: [update]

jobs:
  // Jobs you want to run
Enter fullscreen mode Exit fullscreen mode

Impressions

I am proud to say that it turned out crazy good.


  1. Footnotes can be used. 

Top comments (0)