notes

ms clarity2023. 5. 26.

ms의 analytics 툴 clarity의 설치 스크립트는 이렇게 생겼다.

<script type="text/javascript">
    (function(c,l,a,r,i,t,y){
        //    ^^^^^^^^^^^^^  귀엽...
        c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
        // ...
</script>

Extracting Image Dimensions from Remote Sources2023. 5. 25.

Before

  • til을 위해 repo issues를 cms로 쓰고 있었고,
  • issue body render를 위해 github api reponse 중 bodyHTMLdangerously... 에 넣어주고 있었음.
  • 근데 이러면 next가 해주는 이것저것이 아까우므로,
  • api response 중 body(markdown string)을 mdx renderer에 넘겨서 써야겠다고 생각.

Issues

How?

  • probe-image-size라는 라이브러리가 있고,
  • mdx renderer option의 component 설정시 <img /> -> <Image />로 replace 하면서 요걸 사용해서 width/height 정보를 넘겨주면 됨.
  • 코드는 대충 이런식:
    import probe from "probe-image-size"
    // ...
    const components: MDXRemoteProps["components"] = {
      // ...
      // @ts-expect-error <- ts는 아직 async 컴포넌트를 모르지만 우리는 rsc 세계로 넘어왔으므로 ok.
      img: async ({ src, alt }) => {
        if (!src) return null
    
        const { width, height } = await probe(src ?? "")
        //                        ^^^^^ rsc ftw...
    
        if (!width || !height) return null
    
        return <Image src={src} alt={alt ?? ""} width={width} height={height} />
      },
      // ...
    }
    // ...
    import probe from "probe-image-size"
    // ...
    const components: MDXRemoteProps["components"] = {
      // ...
      // @ts-expect-error <- ts는 아직 async 컴포넌트를 모르지만 우리는 rsc 세계로 넘어왔으므로 ok.
      img: async ({ src, alt }) => {
        if (!src) return null
    
        const { width, height } = await probe(src ?? "")
        //                        ^^^^^ rsc ftw...
    
        if (!width || !height) return null
    
        return <Image src={src} alt={alt ?? ""} width={width} height={height} />
      },
      // ...
    }
    // ...

Result

최종 렌더된 이미지의 url을 보면

Screenshot 2023-05-25 at 17 18 40

next가 잘 처리하고 있음을 알 수 있음.

Conclusion

RSC는 대박이다...

`generateStaticParams`를 사용하는 페이지에서 서버 액션을 호출하면 `405` 에러 (2023-05-24 현재)2023. 5. 24.

좋아요 버튼을 달려고 @vercel/kv 랑 요렇게 저렇게 해보고 있었는데 아래 에러가 무한히 발생했다.

Warning: Detected multiple renderers concurrently rendering the same context provider. This is currently unsupported.
    at AppContainer (/Users/sehyunchung/personal/sehyunchung.dev/node_modules/.pnpm/next@13.4.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/render.js:337:29)
    at AppContainerWithIsomorphicFiberStructure (/Users/sehyunchung/personal/sehyunchung.dev/node_modules/.pnpm/next@13.4.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/render.js:373:57)
    at div
    at Body (/Users/sehyunchung/personal/sehyunchung.dev/node_modules/.pnpm/next@13.4.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/render.js:673:21)
Warning: Detected multiple renderers concurrently rendering the same context provider. This is currently unsupported.
..

검색을 해봐도 뭐가 안나와서 'use server'를 파일에 넣었다 함수에 넣었다 이케저케 해봐도 안됐는데 어쩌다 브라우저 콘솔을 열어보니 Screenshot 2023-05-24 at 17 47 28

네트워크 탭을 확인해보니 Screenshot 2023-05-24 at 17 47 54

요걸로 검색해보니 아래 이슈가 나왔다.

[NEXT-1167] Server Actions 405 "Method Not Allowed" when using generateStaticParams #49408

생각해보면 말이 되는 것 같기도... 근데 안되면 안되는데?

`@vercel/og` Cheatsheet (?)2023. 5. 24.

  1. @vercel/og
  2. Edge Runtime 환경 기반으로 동작함.
  3. 이미지 렌더에 satori를 사용하는데,
  4. app router 사용시 app/og/route.tsx 혹은 app/og.tsx 등등으로 파일을 만들면 됨.
    • .ts도 사용할 수 있으나 고러면 jsx를 사용할 수 없겟쥬
  5. 커스텀 폰트를 사용할 수 있으나 next/font 외에 별도로 로컬에서 폰트 파일을 가져와야 함. (리모트는 아직 안해봄)
    • .ttf, .woff 사용 가능 (.woff2는 안됨)
  6. 대략의 api는 아래와 같음.
    new ImageResponse(
      element: ReactElement,
      options: {
        width?: number = 1200
        height?: number = 630
        emoji?: 'twemoji' | 'blobmoji' | 'noto' | 'openmoji' = 'twemoji', // emoji render에 어떤 lib을 사용할 것인지
        fonts?: {
          name: string,
          data: ArrayBuffer, // 폰트 파일 데이터. fetch(URL).then(res => res.imageBuffer())로 가져오면 된다.
          weight: number,
          style: 'normal' | 'italic'
        }[]
        debug?: boolean = false // true 일 경우 각 element의 border, line-height 등이 표시됨. 
    
        status?: number = 200
        statusText?: string
        headers?: Record<string, string>
      },
    )
    new ImageResponse(
      element: ReactElement,
      options: {
        width?: number = 1200
        height?: number = 630
        emoji?: 'twemoji' | 'blobmoji' | 'noto' | 'openmoji' = 'twemoji', // emoji render에 어떤 lib을 사용할 것인지
        fonts?: {
          name: string,
          data: ArrayBuffer, // 폰트 파일 데이터. fetch(URL).then(res => res.imageBuffer())로 가져오면 된다.
          weight: number,
          style: 'normal' | 'italic'
        }[]
        debug?: boolean = false // true 일 경우 각 element의 border, line-height 등이 표시됨. 
    
        status?: number = 200
        statusText?: string
        headers?: Record<string, string>
      },
    )
  7. 대략의 사용례는 아래와 같음.
    // app/og/route.tsx
    import { ImageResponse } from 'next/server'; // app router 사용시 @verce/og가 포함되어 있음
    
    export const runtime = 'edge';
    
    const font = fetch(new URL('../path/to/font/Font.woff', import.meta.url)).then(
      (res) => res.arrayBuffer(),
    );
    
    export async function GET(request: Request) {
      const fontData = await font;
    
      // query param으로 이런 저런 텍스트를 동적으로 넣을 수 있음.
      const url = new URL(request.url)
      const searchParams = url.searchParams
      const title = searchParams.has("title") ? searchParams.get("title") : null
    
      return new ImageResponse(
        (
          <div
            style={{
            backgroundColor: 'white',
            height: '100%',
            width: '100%',
            fontSize: 100,
            fontFamily: '"Font"',
            paddingTop: '100px',
            paddingLeft: '50px',
          }}
         >
           {title ? title : 'Hello World!'}
         </div>
       ),
       {
         width: 1200,
         height: 630,
         fonts: [
           {
             name: 'Font',
             data: fontData,
             style: 'normal',
           },
         ],
       },
     );
    }
    // app/og/route.tsx
    import { ImageResponse } from 'next/server'; // app router 사용시 @verce/og가 포함되어 있음
    
    export const runtime = 'edge';
    
    const font = fetch(new URL('../path/to/font/Font.woff', import.meta.url)).then(
      (res) => res.arrayBuffer(),
    );
    
    export async function GET(request: Request) {
      const fontData = await font;
    
      // query param으로 이런 저런 텍스트를 동적으로 넣을 수 있음.
      const url = new URL(request.url)
      const searchParams = url.searchParams
      const title = searchParams.has("title") ? searchParams.get("title") : null
    
      return new ImageResponse(
        (
          <div
            style={{
            backgroundColor: 'white',
            height: '100%',
            width: '100%',
            fontSize: 100,
            fontFamily: '"Font"',
            paddingTop: '100px',
            paddingLeft: '50px',
          }}
         >
           {title ? title : 'Hello World!'}
         </div>
       ),
       {
         width: 1200,
         height: 630,
         fonts: [
           {
             name: 'Font',
             data: fontData,
             style: 'normal',
           },
         ],
       },
     );
    }
  8. tailwind 사용이 가능한데 아직 experimental이 붙어있고 className 말고 tw를 사용하도록 되어있음.
  9. Hobby plan일 경우 단일 function당 1MB 제한이 있어 한글 커스텀 폰트를 추가하긴 쉽지 않았음.
  10. sehyunchung.dev에 적용해본 결과 -> https://sehyunchung.dev/og?title=암온더넧렙을&description=절대적룰을지켜

Vercel Edge Function size limit2023. 5. 22.

@vercel/og로 og image generation을 하는 김에 폰트도 맞춰보려고 일케절케 하다보니

Error: The Edge Function "api/og" size is X MB and your plan size limit is 1 MB. Learn More: https://vercel.link/edge-function-size

라면서 빌드가 터짐.

왜냐?

  1. og image generation에 커스텀 폰트를 사용하려면 .ttf 혹은 .woff (.woff2는 안됨)를 fetch로 불러와서 arrayBuffer로 만들어야 함
  2. 영어 폰트의 자수는 72자
  3. 근데 한글 폰트는 서브셋 처리를 해도 2,350자
  4. 그래서 한글 폰트를 일단 불러오면 1MB 안엔 안들어감 Screenshot 2023-05-22 at 23 57 33
  5. 돈을 더 내거나 더 줄인 서브셋을 만들어야 함
  6. 돈을 더 낼 순 없다 왜냐면 암것도 모르는 영어권 놈들(...)이 책정한 가격이니까
  7. 자수를 더 줄인 서브셋 만들기는 아직 방법도 모르므로 이후로 미룸
  8. 일단 한글 커스텀 폰트를 뺐더니
  9. 빌드에 성공함
  10. 영어!!!! ㅠㅠㅠ

Changing Learning Environment IS Beneficial2023. 5. 22.

Q: How important is routine when it comes to learning? For example, is it important to have a dedicated study area?

A: Not at all. Most people do better over time by varying their study or practice locations. The more environments in which you rehearse, the sharper and more lasting the memory of that material becomes—and less strongly linked to one “comfort zone.” That is, knowledge becomes increasingly independent of surroundings the more changes you make—taking your laptop onto the porch, out to a café, on the plane. The goal, after all, is to be able to perform well in any conditions. Changing locations is not the only way to take advantage of the so-called context effect on learning, however. Altering the time of day you study also helps, as does changing how you engage the material, by reading or discussing, typing into a computer or writing by hand, reciting in front of a mirror or studying while listening to music: Each counts as a different learning “environment” in which you store the material in a different way.

Benedict Carey, How We Learn. Random House Trade Paperbacks; Reprint edition (June 9, 2015) p. 201

Why does Windows use `\` while everyone else uses `/`?2023. 5. 20.

Me: "Why does Windows use \ while everyone else uses /?"

GPT-4: "The reason for the difference lies in the history of the two operating systems and the choices made by their developers.

Microsoft's Windows OS uses backslashes (\) as a path separator due to its legacy of MS-DOS. MS-DOS was heavily influenced by an older operating system named CP/M, which did not have a hierarchical file system, meaning it did not have directories/folders and therefore did not need a directory separator character. When Microsoft introduced directories in MS-DOS 2.0, they needed a character to act as a directory separator, but the forward slash (/) was already being used as the switch character (a kind of option flag) for command-line arguments. Therefore, Microsoft chose the backslash (\) to avoid breaking compatibility with existing software.

On the other side, Unix and Unix-like systems (including Linux and MacOS), use the forward slash (/) as a path separator. The forward slash was chosen as a directory separator in Unix due to its simplicity and ease of use in a command line interface. The forward slash was available because Unix uses the hyphen - for command-line options, not the forward slash.

It's worth noting that although Windows primarily uses backslashes for paths, it does understand forward slashes in most contexts. However, because the backslash is the standard, Windows software often expects paths to be written with backslashes and can behave unexpectedly if they're not."

Split string into array2023. 5. 18.

words="hello hello how low?"

array=($words);

for word in "${array[@]}"; do
  echo $word
done
words="hello hello how low?"

array=($words);

for word in "${array[@]}"; do
  echo $word
done
hello
hello
how
low?
hello
hello
how
low?

Tags