0

我正在尝试使用 nextjs 创建一个静态博客。

我想使用@emotion/styled来设置代码块的样式,但我遇到了一些水合问题。

我在我的博客中创建了一个自定义<Code>组件来设置代码片段的样式。

我正在使用@ts-stack/markdown 来解析博客内容并添加一个简单的块规则来显示此示例之后的代码片段

我在控制台中收到此错误:

Warning: Prop `dangerouslySetInnerHTML` did not match. 
Server: "<p>Some text</p>\n<div class=\"css-129r3k1\"><div class=\"css-52td2g\">JS</div><div class=\"css-pxcwrp\">\n<pre><code>const mycode = \"\"\n</code></pre>\n</div></div>" 
Client: "<p>Some text</p>\n<style data-emotion=\"css 129r3k1\">.css-129r3k1{margin:0 auto;position:relative;max-width:850px;}</style><div class=\"css-129r3k1\"><style data-emotion=\"css 52td2g\">.css-52td2g{position:absolute;right:0;border-radius:10px 10px 0 0;background-color:#f2f2fd;padding:6px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;min-width:50px;font-size:var(--normal-text-size);text-align:center;font-weight:bold;color:purple;-webkit-transform:translateX(-100%) translateY(-100%);-moz-transform:translateX(-100%) translateY(-100%);-ms-transform:translateX(-100%) translateY(-100%);transform:translateX(-100%) translateY(-100%);}</style><div class=\"css-52td2g\">JS</div><style data-emotion=\"css pxcwrp\">.css-pxcwrp{margin-top:32px;font-size:var(--feature-text-size);padding:32px;background-color:#f2f2fd;border-radius:12px;overflow-x:auto;line-height:28px;}</style><div class=\"css-pxcwrp\">\n<pre><code>const mycode = \"\"\n</code></pre>\n</div></div>"

看起来服务器生成的代码正确地将样式放在 head 元素中,但是当客户端呈现时,它希望将样式标签与内容内联。

我不确定为什么会这样......

这是我的代码:

这是自定义<Code>组件:

import emotion from "@emotion/styled"
import { FC } from "react"
type valueof<T> = T[keyof T]

export const LANGS = ["ts", "js"] as const
export type SupportedLangs = valueof<typeof LANGS>

type Props = {
  dangerouslySetInnerHTML?:
    | {
        __html: string
      }
    | undefined
  lang: SupportedLangs
}

export const Code: FC<Props> = ({
  children,
  lang,
  dangerouslySetInnerHTML,
}) => (
  <Wrapper>
      <Lang>{lang.toString().toUpperCase()}</Lang>
    <Content dangerouslySetInnerHTML={dangerouslySetInnerHTML}>
      {children}
    </Content>
  </Wrapper>
)

const Lang = emotion.div`
  position: absolute;
  right:0;
  border-radius: 10px 10px 0 0;
  background-color: #f2f2fd;
  padding: 6px;
  width: fit-content;
  min-width: 50px;
  font-size: var(--normal-text-size);
  text-align: center;
  font-weight: bold;
  color: purple;
  transform: translateX(-100%) translateY(-100%);
`

export const Content = emotion.div`
  margin-top: 32px;
  font-size: var(--feature-text-size);
  padding: 32px;
  background-color: #f2f2fd;
  border-radius: 12px;
  overflow-x: auto;
  line-height: 28px;
`
export const Wrapper = emotion.div`
  margin: 0 auto;
  position: relative;
  max-width: 850px;
`

这是我的pages/posts/[id].tsx文件。

import fs from "fs"
import { GetStaticPaths, GetStaticProps } from "next"
import { Marked } from "@ts-stack/markdown"
import matter, { GrayMatterFile } from "gray-matter"
import ReactDOMServer from "react-dom/server"
import { ErrorMessage } from "../../components/errorMessage"
import { Code, LANGS, SupportedLangs } from "../../components/code"

Marked.setBlockRule(
  /^@@@ *(\w+):?(js|ts)?\n([\s\S]+?)\n@@@/,
  function (execArr) {
    const channel = execArr?.[1]
    const param1 = execArr?.[2]
    const content = execArr?.[3]

    switch (channel) {
      case "code": {
        return (
          ReactDOMServer.renderToStaticMarkup(
            <Code
              lang={param1 as SupportedLangs}
              dangerouslySetInnerHTML={{ __html: Marked.parse(content) }}
            ></Code>
          )
        )
      }
    }
  }
)

type Props = { id: string } & GrayMatterFile<string>

const postsPath = "./posts"

export const getStaticProps: GetStaticProps<Props, { id: string }> = ({
  params,
}) => {
  const file = fs.readFileSync(postsPath + `/${params!.id}.md`).toString()
  const parsed = matter(file)
  return Promise.resolve({
    props: {
      id: params!.id,
      ...parsed,
      orig: parsed.orig.toString(),
      content: Marked.parse(parsed.content),
    },
  })
}

export const getStaticPaths: GetStaticPaths = async () => ({
  fallback: false,
  paths: fs
    .readdirSync(postsPath)
    .map((x) => "/posts/" + x.substr(0, x.length - 3)),
})

export default function Post({ content, data }: Props) {
  return (
    <div>
      <p>{data.date}</p>
      <div dangerouslySetInnerHTML={{ __html: content }}></div>
    </div>
  )
}

我的示例发布降价文件:

---
date: 2022/02/14
---

Some text

@@@ code:js
\```
const mycode = ""
\```
@@@
4

0 回答 0