Introduction
This tutorial provides a straightforward method to add a copy-to-clipboard button in an MDX file within a Next.js application.
Technology
- MDX: A powerful markup language that combines the best of Markdown and JSX, allowing you to write content with components and logic.
- Next.js (App Router): A React framework for building server-rendered and static web applications with routing and data fetching.
- React: A popular JavaScript library for building user interfaces.
- Tailwind CSS: A utility-first CSS framework for rapidly building custom designs.
- Bright: A modern React server component for syntax highlighting
Setup
Next Mdx Remote
Next Mdx Remote is a library that allows you to load MDX files remotely, making it easy to create dynamic content that can be updated without redeploying your application.
To use Next Mdx Remote, you can install it using the following command:
npm install next-mdx-remote
Once installed, you can import it into your application and use it to load MDX files.
CustomMDX
Create a new file named mdx.tsx
in the components
directory.
// components/mdx.tsx import { MDXRemote } from 'next-mdx-remote/rsc'; import { MDXComponents } from 'mdx/types'; export const mdxComponents: MDXComponents = {}; export function CustomMDX({ children, components, }: { children: string; components: MDXComponents; }) { return ( <MDXRemote source={children} components={{ ...mdxComponents, ...(components || {}) }} /> ); }
Custom Components
You can add custom components to your MDX files using the mdxComponents
option. This allows you to define your own components that can be used within your MDX content.
Now let's add a custom component to our MDX file.
Create a new file named Pre.tsx
in the components
directory.
// components/Pre.tsx 'use client'; import { useState, useRef } from 'react'; import { Copy, Check } from 'lucide-react'; const Pre = (props: any) => { const textInput = useRef<any>(null); const [copied, setCopied] = useState(false); const onCopy = () => { setCopied(true); navigator.clipboard.writeText(textInput?.current?.textContent); setTimeout(() => { setCopied(false); }, 2000); }; return ( <div className='relative right-[20px] top-[20px]'> <span ref={textInput} className='hidden'> {props.children} </span> <button aria-label='Copy code' type='button' className='h-4 w-4' onClick={onCopy} > {copied ? ( <Check className='text-[#80d1a9]' /> ) : ( <Copy className='text-white' /> )} </button> </div> ); }; export default Pre;
Usage
Add Pre component
Import Pre component in your MDX file.
// components/mdx.tsx import { Code } from 'bright'; import Pre from './Pre'; Code.theme = { dark: 'github-dark', light: 'github-light', }; export const mdxComponents: MDXComponents = { pre: (props) => ( <div className='flex flex-row gap-2 bg-[#0d1117]'> <Code className='w-full'>{props.children}</Code> <Pre {...props} /> </div> ), };
Use CustomMDX
Import CustomMDX in page.tsx
// blog/[slug]/page.tsx import { notFound } from 'next/navigation'; import getBlogs from '@/db/get-blogs'; import { CustomMDX } from '@/components/mdx'; export default function Blog({ params }: { params: any }) { const blog = getBlogs().find((post) => post.slug === params.slug); if (!blog) { return notFound(); } return ( <div className='mx-auto mt-40 w-11/12'> <h1 className='text-start text-xl font-bold sm:text-4xl'> {blog.metadata.title} </h1> <div className='mb-4 mt-2 flex items-center justify-between'> <p className='text-lg text-neutral-700 dark:text-neutral-300'> <span className='flex flex-row items-center gap-2'> {blog.metadata.description} </span> </p> </div> <article className='prose prose-zinc mx-auto my-10 max-w-none dark:prose-invert md:prose-lg lg:prose-xl'> <CustomMDX components={{}}>{blog.content}</CustomMDX> </article> </div> ); }