Skip to content

Commit

Permalink
fix(changelog): Fix hydration errors because of nested <a> tags (#1…
Browse files Browse the repository at this point in the history
  • Loading branch information
lforst committed Aug 9, 2024
1 parent b3a09d1 commit 9821923
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 40 deletions.
4 changes: 2 additions & 2 deletions apps/changelog/src/app/changelog/[slug]/loading.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import Article from '@/client/components/article';
import {LoadingArticle} from '@/client/components/article';

export default function Loading() {
return (
<div className="relative min-h-[calc(100vh-8rem)] w-full mx-auto grid grid-cols-12 bg-gray-200">
<div className="col-span-12 md:col-start-3 md:col-span-8">
<div className="max-w-3xl mx-auto px-4 p-4 sm:px-6 lg:px-8 mt-16">
<Article loading />
<LoadingArticle />
</div>
</div>
</div>
Expand Down
46 changes: 21 additions & 25 deletions apps/changelog/src/app/changelog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import { Fragment, Suspense } from "react";
import { type Changelog } from "@prisma/client";
import type { Metadata, ResolvingMetadata } from "next";
import { unstable_cache } from "next/cache";
import Link from "next/link";
import { notFound } from "next/navigation";
import { getServerSession } from "next-auth/next";
import { MDXRemote } from "next-mdx-remote/rsc";
import {Fragment, Suspense} from 'react';
import {type Changelog} from '@prisma/client';
import type {Metadata, ResolvingMetadata} from 'next';
import {unstable_cache} from 'next/cache';
import Link from 'next/link';
import {notFound} from 'next/navigation';
import {getServerSession} from 'next-auth/next';
import {MDXRemote} from 'next-mdx-remote/rsc';

import { prismaClient } from "@/server/prisma-client";
import Article from "@/client/components/article";
import ArticleFooter from "@/client/components/articleFooter";
import { authOptions } from "@/server/authOptions";
import { mdxOptions } from "@/server/mdxOptions";
import {prismaClient} from '@/server/prisma-client';
import {Article} from '@/client/components/article';
import ArticleFooter from '@/client/components/articleFooter';
import {authOptions} from '@/server/authOptions';
import {mdxOptions} from '@/server/mdxOptions';

export const dynamic = "force-dynamic";
export const dynamic = 'force-dynamic';

export async function generateMetadata(
{ params }: { params: { slug: string } },
{params}: {params: {slug: string}},
parent: ResolvingMetadata
): Promise<Metadata> {
let changelog: Changelog | null = null;
try {
changelog = await getChangelog(params.slug);
} catch (e) {
return { title: (await parent).title };
return {title: (await parent).title};
}

return {
Expand All @@ -39,7 +39,7 @@ export async function generateMetadata(
}

const getChangelog = unstable_cache(
async (slug) => {
async slug => {
try {
return await prismaClient.changelog.findUnique({
where: {
Expand All @@ -53,15 +53,11 @@ const getChangelog = unstable_cache(
return null;
}
},
["changelog-detail"],
{ tags: ["changelog-detail"] }
['changelog-detail'],
{tags: ['changelog-detail']}
);

export default async function ChangelogEntry({
params,
}: {
params: { slug: string };
}) {
export default async function ChangelogEntry({params}: {params: {slug: string}}) {
const changelog = await getChangelog(params.slug);

if (!changelog) {
Expand Down Expand Up @@ -112,7 +108,7 @@ export default async function ChangelogEntry({
>
<Suspense fallback={<Fragment>Loading...</Fragment>}>
<MDXRemote
source={changelog?.content || "No content found."}
source={changelog?.content || 'No content found.'}
options={
{
mdxOptions,
Expand Down
4 changes: 2 additions & 2 deletions apps/changelog/src/app/changelog/loading.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Fragment} from 'react';

import Header from './header';
import Article from '@/client/components/article';
import {LoadingArticle} from '@/client/components/article';

export default function Loading() {
return (
Expand All @@ -24,7 +24,7 @@ export default function Loading() {
</div>
<div className="col-span-12 md:col-span-8">
<div className="max-w-3xl mx-auto px-4 pb-4 sm:px-6 md:px-8 mt-28">
<Article loading />
<LoadingArticle />
</div>
</div>
<div className="hidden md:block md:col-span-2 pl-5 pt-10">
Expand Down
32 changes: 31 additions & 1 deletion apps/changelog/src/app/changelog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import Header from './header';
import {getChangelogs} from '../../server/utils';
import {ChangelogEntry, ChangelogList} from '@/client/components/list';
import {startSpan} from '@sentry/nextjs';
import type {Plugin} from 'unified';
import {visit} from 'unist-util-visit';
import {Element} from 'hast';

export const dynamic = 'force-dynamic';

Expand All @@ -21,7 +24,20 @@ export default async function Page() {
() =>
Promise.all(
changelogsWithPublishedAt.map(async (changelog): Promise<ChangelogEntry> => {
const mdxSummary = await serialize(changelog.summary || '');
const mdxSummary = await serialize(
changelog.summary || '',
{
mdxOptions: {
rehypePlugins: [
// Because we render the changelog entries as <a> tags, and it is not allowed to render <a> tags
// within other a tags, we need to strip away the <a> tags inside the previews here.
// @ts-ignore
stripLinks,
],
},
},
true
);
return {
id: changelog.id,
title: changelog.title,
Expand Down Expand Up @@ -52,3 +68,17 @@ export function generateMetadata(): Metadata {
},
};
}

const stripLinks: Plugin = () => {
return tree => {
return visit(tree, 'element', (node: Element) => {
if (node.tagName === 'a') {
node.tagName = 'span';
if (node.properties) {
delete node.properties.href;
delete node.properties.class;
}
}
});
};
};
10 changes: 2 additions & 8 deletions apps/changelog/src/client/components/article.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,14 @@ type ArticleProps = {
title?: string;
};

export default function Article({
export function Article({
title = '',
image,
tags = [],
date = null,
children,
loading,
className,
}: ArticleProps) {
if (loading) {
return <LoadingArticle />;
}

return (
<article className={`bg-white rounded-lg shadow-lg mb-8 ${className}`}>
{/* this needs to be a plain <img> next/image doesn't work here because of redirects we do */}
Expand All @@ -43,7 +38,6 @@ export default function Article({
<div className="flex flex-wrap gap-1 py-1">
{Array.isArray(tags) && tags.map(tag => <CategoryTag key={tag} text={tag} />)}
</div>

<div className="prose max-w-none text-gray-700 py-2">{children}</div>
<dl>
<dd className="text-xs leading-6 text-gray-400">
Expand All @@ -56,7 +50,7 @@ export default function Article({
);
}

function LoadingArticle() {
export function LoadingArticle() {
return (
<article className="bg-white rounded-lg shadow-lg mb-8">
<div className="p-6">
Expand Down
4 changes: 2 additions & 2 deletions apps/changelog/src/client/components/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {MDXRemote, MDXRemoteSerializeResult} from 'next-mdx-remote';
import Link from 'next/link';
import {parseAsArrayOf, parseAsInteger, parseAsString, useQueryState} from 'nuqs';
import {Fragment} from 'react';
import Article from './article';
import {Article} from './article';
import {Pagination} from './pagination';
import {CategoryTag} from './tag';

Expand Down Expand Up @@ -149,7 +149,7 @@ export function ChangelogList({changelogs}: {changelogs: ChangelogEntry[]}) {
<div className="flex-1 border-t-[1px] border-gray-400" />
</div>
)}
<Link href={`/changelog/${changelog.slug}`} key={changelog.id}>
<Link href={`/changelog/${changelog.slug}`}>
<Article
className="fancy-border"
key={changelog.id}
Expand Down

0 comments on commit 9821923

Please sign in to comment.