This post offers a guide on how to effectively use Markdown syntax and MDX to create well-structured and visually appealing blog posts using the samwise
template.
#Writing Content with Markdown
Markdown is a lightweight markup language commonly used for formatting plain text in a simple, readable way. In the context of MDX
, which stands for Markdown + JSX, Markdown allows you to write content with familiar syntax (like #
for headers, *
for lists, and []()
for links) while also enabling the embedding of React components directly within the Markdown file.
This combination makes MDX especially useful in projects like blogs or documentation sites, as it allows developers to mix static content (written in Markdown) with interactive elements (created using JSX components) in a seamless way.
#Article Title and Header
In the samwise
template, the title header is not part of the MDX components. This documentation covers the header explicitly because it represents a core structural element that bridges metadata and presentation for every blog post.
The header is automatically generated from the title
property in the metadata
object in your post's corresponding page.mdx
. It is rendered using the src/app/blog/[slug]/header.tsx
component.
The header includes:
- Title: From the
metadata.title
property. - Date: From the
metadata.date
property. - Author: Uses the
AUTHOR
constant fromsrc/config.ts
; override withmetadata.author
andmetadata.authorUrl
. - View Count: This feature only increments in the production environment. For local development modifications, you can adjust the
src/components/Views/index.tsx
component.
Example metadata structure:
export const metadata = { title: "Improving Blog Documentation", date: "2024-02-15", author: "Jane Doe", authorUrl: "https://janedoe.com", ... }
Here's an example of how the header appears at the top of this blog post:
To update the default implementation for the header all blog posts, update the src/app/blog/[slug]/header.tsx
file.
#Headings
Headings are important for SEO and readability. They help organize the content of your blog post and make it easier to read.
To create a heading, use the #
symbol followed by the heading text. For example:
# My Heading
If you want to add an anchor to the heading for when user hovers over it, you can use the following syntax:
## My Heading [#my-heading]
This will create a heading with an anchor that you can use to link to the heading. When a user clicks on the anchor, they will be redirected to the heading.
#Subheadings
Subheadings are smaller headings that help break up the content of your blog post and make it easier to read.
#Here's a sub-header with an anchor
You also have sub-sub headings:
#Here's a sub-sub-header with an anchor
#Table of Contents
The table of contents menu is toggled via a floating button on the blog post. To ensure your heading is added to the table of contents, use an anchor on your heading:
# My Heading [#this-heading-will-appear-in-table-of contents]
All headings with anchors will automatically be added to the table of contents.
Note
The floating button for the Table of Contents will only render if your post has at least three headings with anchors.
#Paragraphs
Paragraphs are a block of text that are displayed in a paragraph.
An example of two paragraphs is:
#Italic
To create italic text, use the *
symbol followed by the text. For example:
*Italic text*
Here is an example of italic text:
Italic text
#Bold
To create bold text, use the **
symbol followed by the text. For example:
**Bold text**
Here is an example of bold text:
Bold text
#Strikethrough
To create strikethrough text, use the ~~
symbol followed by the text. For example:
~~Strikethrough text~~
Here is an example of strikethrough text:
Strikethrough text
#Comments
If you wish to add content to a post but do not want it to display on the webpage, do the following:
Hello there,
{
/*
I'm not ready to reveal this section to you
*/
}
My name is Patrick.
Output:
Hello there,
My name is Patrick.
Note that the "I'm not ready to reveal this section to you" text is not rendered.
#Checkbox
To create a checkbox, use the [ ]
symbol followed by the text. For example:
- [ ] Checkbox
Here is an example of a checkbox:
- Walk my beautiful doggo
If you want to create a checkbox that is checked, you can use the [x]
symbol followed by the text. For example:
- [x] Checkbox
Here is an example of a list of checkboxes, checked and unchecked:
- Walk my beautiful doggo
- Do the gardening
- Code for 5 hours straight
#Newlines
A newline is a text break that moves the content to the next line without creating a new paragraph or adding additional vertical spacing.
To create a newline in Markdown, add the "\
" (backslash) character at the end of the line where you want the break to occur. This technique allows you to control line breaks without introducing the extra spacing that comes with paragraphs.
so much depends \ upon a red wheel \ barrow glazed with rain \ water beside the white \ chickens
Here is an example of text using newlines:
so much depends
upon
a red wheel
barrow
glazed with rain
water
beside the white
chickens
Use newlines when you want to maintain a visual flow in your content without creating the visual separation of a new paragraph. For example, in poetry, addresses, or to preserve line breaks in quoted content.
#Lists
Lists are a block of text that are displayed in a list.
#Unordered Lists
You can create unordered lists using asterisks (*):
* Item 1
* Item 2
* Item 3
Here is an example of an unordered list:
- Item 1
- Item 2
- Item 3
#Ordered Lists
You can create ordered lists using numbers:
1. Item 1
2. Item 2
3. Item 3
Here is an example of an ordered list:
- Item 1
- Item 2
- Item 3
#Tables
To create a table using Markdown syntax:
| Column 1 | Column 2 | Column 3 | | -------- | -------- | -------- | | Value 1 | Value 2 | Value 3 | | Value 4 | Value 5 | Value 6 |
This will render the table as:
Column 1 | Column 2 | Column 3 |
---|---|---|
Value 1 | Value 2 | Value 3 |
Value 4 | Value 5 | Value 6 |
#Blockquotes
Blockquotes are a block of text that are displayed in a blockquote.
An example of a blockquote is:
> This is a blockquote
Here is a blockquote with a citation:
The only way to do great work is to love what you do. If you haven't found it yet, keep looking. Don't settle. — Steve Jobs
#Break
A break is a block of text that are displayed in a break.
An example of a break is:
***
Here is a break:
This is useful for adding a break in your text to separate sections.
#Images
Images can be added using standard Markdown syntax and are automatically optimized using next-image
:
![Alt text](/images/example.webp)
Here is an example of an image:
- All images should be stored in the
public/images
directory. - Refer to the
mdx-components.tsx
if you wish to change the default optimization or animation configuration. - Use WebP format images (.webp) to improve loading speed (WebP provides ~30% better compression than PNG/JPEG).
#Externally Referenced Images
If you wish to use an image from an external URL. For example:
![My Image](https://example.com/image.jpg)
You must add it to remotePatterns
in next.config.mjs
file. Otherwise it will not be displayed and you will see an error in the console
See NextJS documentation on next/image and remotePatterns for more information.
#Image Captions
Captions can be added to images using the <Caption>
component below the image.
![My Image](/images/guide-to-writing-your-first-samwise-post-using-markdown/patrickprunty.png)
<Caption>Patrick Prunty: Creator of this blog template</Caption>
Here is an example of an image with a caption:
The Creator of this blog template, Patrick Prunty on the Camino de Santiago
#Custom Styled Image
If you wish to add specific styling, resize the image, or make the image a loading priority, you can directly import the Image
component from next/image
and use it directly in your blog post.
<div className="flex items-center justify-center mt-10 mb-10">
<Image
src="/images/_placeholders/avatar.svg"
alt="Example Avatar"
className="rounded-full object-cover w-40 h-40"
width={140}
height={140}
unoptimized
/>
</div>
<Caption>Custom Styled Image</Caption>
Here is an example of an image with custom styling; positioned centrally, fully rounded borders, responsive sizing and priority loading:
Custom Styled Image#Floating Images
It is sometimes desirable to float and image beside text. To do this, you can
use next/image
and tailwindcss
.
For example, suppose you want to render a book cover beside a review on desktop devices and above the review on mobile devices:
<Image
src="/images/guide-to-writing-your-first-samwise-post-using-markdown/ulysses.png"
alt="Ulysses Book Cover"
className="bg-gray-100 block mt-2 mb-5 mx-auto w-full sm:w-80 md:float-right sm:ml-5 sm:mb-5"
width={160}
height={160}
priority
/>
Ulysses by James Joyce
James Joyce's Ulysses transforms a single day in Dublin—June 16, 1904—into an epic exploration of human consciousness. The novel follows Leopold Bloom, an advertising salesman, as he navigates the city streets, paralleling Homer's Odyssey in a modern urban setting. Through Bloom's journey, alongside the young artist Stephen Dedalus and Bloom's wife Molly, Joyce weaves an intricate tapestry of human experience.
What makes Ulysses revolutionary is Joyce's masterful stream-of-consciousness technique, which plunges readers directly into the minds of his characters. Each of the novel's eighteen episodes employs a unique style, ranging from newspaper headlines to musical rhetoric, culminating in Molly Bloom's famous unpunctuated soliloquy.
While the novel's dense allusions and experimental style can challenge readers, its core strength lies in its profound humanity. Joyce finds extraordinary meaning in ordinary moments, transforming Dublin into a microcosm of civilization. In capturing one day in exquisite detail, Ulysses achieves something remarkable: it encompasses the entire range of human experience, from the sacred to the profane.
figure wide
#Photo Grid
To create a photo grid, you can use the <PhotoGrid>
component.
// src/posts/my-article/page.mdx
// Export is needed here even though it is referenced in the same file because of MDX
export const photoGridImages = [
"/images/_placeholders/avatar.svg",
"/images/_placeholders/avatar.svg",
"/images/_placeholders/avatar.svg"
];
export const metadata = {
... your metadata here
}
Here is an example of a photo grid:
<PhotoGrid images={photoGridImages} />
Here is an example of a photo grid:
Note
The PhotoGrid
component displays three columns on large screens and two on small screens. To maintain even rows, it
omits the last photo if it creates an uneven row. For example, with 4 photos: large screens show one row of three,
omitting the fourth, while small screens display two rows of two, showing all photos.
#Links
Links are a block of text that are displayed in a blockquote.
An example of a link is:
[My Link](https://example.com)
Here is a link to my website.
#Link with an Image
You can also wrap links around other content to make it clickable:
[![Clickable image](/images/_placeholders/avatar.svg)](https://patrickprunty.com)
<Caption>Click me to navigate to my website!</Caption>
Here is a clickable image:
Click me to navigate to my website!
If you do not want to enable the default animation onClick
, you must use the Link
and MemoizedImage
component directly with focusable={false}
:
import Link from 'next/link';
...
<Link
href="https://patrickprunty.com"
>
<MemoizedImage
src="/images/_placeholders/avatar.svg"
alt="Link to my website"
width={620}
height={500}
loading="lazy"
priority={false}
focusable={false}
/>
</Link>
#Admonitions
Admonitions are a block of text that are displayed in a blockquote.
An example of an admonition is:
<Admonition type="info" title="Info">
This is a note
</Admonition>
Info
This is a note
Other types of admonitions include type "warning", "tip", "danger" and "note":
Warning
This is a warning
Danger
This is a danger
Tip
This is a tip
Note
This is a note
#Code Blocks
Code is a block of text that are displayed in a code block.
Code blocks are escaped with three backticks followed by the language of the code and a newline, code body and then another three backticks.
Here is an example of a code block for C++:
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
#Footnotes
Footnotes are a block of text that are displayed in a footnote.
An example of a footnote is:
Here is a footnote.<Ref id="1" />
<FootNotes>
<FootNote id="1">Here is a reference to a footnote with a link to <a href="https://patrickprunty.com">my website</a>.</FootNote>
</FootNotes>
This is a footnote.[1]. You will see this in the footer of the blog post or by clicking on the footnote number.
Here is another footnote on a blockquote with citation:
"Two roads diverged in a wood, and I— I took the one less traveled by, And that has made all the difference." — Robert Frost [2]
#Audio (mp3)
You can embed mp3 files to preview and play in your blog post by using the MP3
component:
<MP3 src="/documents/audio.mp3"/>
Output:
Example audio with some weird noise.#PDFs
You can embed PDF files directly into your blog posts using the <PDF>
component. This allows you to display documents, presentations, or any other PDF content seamlessly within your post.
<PDF file="/documents/bitcoin.pdf" caption="The Bitcoin Whitepaper"/>
Output:
The <PDF>
component is designed to be responsive across different devices:
- Desktop Devices: The PDF is embedded directly in the page with full interactive controls, such as scrolling through pages, zooming, and printing.
- Mobile Devices: Due to limitations on some mobile devices, especially iOS devices, embedded PDFs may only display the first page without interactive controls. To enhance the user experience, the component makes the PDF clickable and adds "(click to open)" in the caption, indicating that users can tap to open the full document in a new tab or the default PDF viewer.
#Social Embeds
#YouTube Videos
You can embed YouTube videos into your blog posts using the following syntax:
<YouTube videoId="q86g1aop6a8" />
The YouTube videoId
is the ID of the YouTube video.
You can find the video ID by clicking on the YouTube video and then copying the ID from the URL after v=
in the URL: https://www.youtube.com/watch?v=q86g1aop6a8
.
#TikTok Videos
To embed TikTok videos, use the videoUrl parameter. This is the complete URL of the TikTok video you want to embed.
<TikTok videoUrl="https://www.tiktok.com/@ashleypicock12345678/video/7430963230823959854"/>
A random TikTok video used for example purposes
#Strava Activity
You can embed your Strava activities to showcase your recent workouts or race results. Just specify the activityId, which you can find in the URL of your activity on Strava.
<Strava activityId="10125503113" />
Embedding LinkedIn posts enables you to share professional updates, articles, and discussions directly within your blog, providing readers with a richer context and up-to-date insights. Use the postUrl parameter to specify the link to your LinkedIn post.
How to Get the postUrl for LinkedIn Embeds
- Open the LinkedIn post you want to embed.
- Click the "Share" button on the post and select "Embed."
- Copy the URL in the embed code that appears, and paste it into the postUrl field in your blog’s Markdown or component code.
<LinkedIn postUrl="https://www.linkedin.com/embed/feed/update/urn:li:share:7259535098184663041" />
#X (Tweets)
To share tweets, you can embed them by providing the full tweetUrl. This allows you to include real-time Twitter content such as status updates or comments.
<Twitter tweetUrl="https://twitter.com/elonmusk/status/1853612871877329188?ref_src=twsrc%5Etfw" />
#Star Ratings
The StarRating
component is a reusable React component for displaying and interacting with a star-based rating system. It supports locked and interactive states, customizable ratings, and dynamic callbacks.
Props
Prop Name | Type | Default | Description |
---|---|---|---|
initialValue | number | 0 | Initial number of highlighted stars (0–5). |
onChange | (value: number) => void | undefined | Callback triggered on rating change (if unlocked). |
locked | boolean | true | Whether the rating is locked (non-editable). |
Usage
<StarRating initialValue={3} />
Output:
The StarRating
component enables blog posts for rating items, for example:
<div className="justify-center flex">
<Image
src="/images/guide-to-writing-your-first-samwise-post-using-markdown/ulysses.png"
alt="Ulysses Book Cover"
className="bg-gray-100 block"
width={160}
height={160}
priority
/>
<StarRating initialValue={3} />
</div>
Output:
#Grid
The Grid
component allows you to display content in a responsive grid layout. You can specify the number of columns, the gap between items, and additional customizations.
Props
Prop Name | Type | Default | Description |
---|---|---|---|
columns | number | — | Number of columns in the grid layout. |
gap | string | 'gap-1' | The gap between grid items, using Tailwind CSS spacing classes. |
className | string | '' | Additional CSS classes for the grid container. |
children | ReactNode | — | The content to display inside the grid. |
Usage
The Grid
component can be used to display images, cards, or other content in a grid layout. Here’s an example of how
to use a grid in your MDX file:
<Grid columns={3} gap="gap-4" className="mt-6">
<div className="bg-gray-200 p-4 text-center">Item 1</div>
<div className="bg-gray-300 p-4 text-center">Item 2</div>
<div className="bg-gray-400 p-4 text-center">Item 3</div>
<div className="bg-gray-500 p-4 text-center">Item 4</div>
<div className="bg-gray-600 p-4 text-center">Item 5</div>
<div className="bg-gray-700 p-4 text-center">Item 6</div>
</Grid>
<Caption>A simple 3-column grid with 4px gaps</Caption>
Output:
#Carousels
The Carousel
component enables you to display content in a horizontally scrollable layout. This is perfect for
showcasing images, testimonials, or other items that require horizontal scrolling.
Props
Prop Name | Type | Default | Description |
---|---|---|---|
items | ReactNode[] | — | An array of React nodes to display in the carousel. |
itemClassName | string | '' | CSS class for individual carousel items. |
containerClassName | string | '' | CSS class for the carousel container. |
Usage
Here is how to use the Carousel
component in your MDX file:
<Carousel
items={[
<Image
src="/images/_placeholders/avatar.svg"
alt="Example Avatar"
className="object-cover w-full"
width={300}
height={300}
unoptimized
/>,
<Image
src="/images/_placeholders/avatar.svg"
alt="Example Avatar"
className="object-cover w-full"
width={300}
height={300}
unoptimized
/>,
<Image
src="/images/_placeholders/avatar.svg"
alt="Example Avatar"
className="object-cover w-full"
width={300}
height={300}
unoptimized
/>,
]}
itemClassName="bg-gray-100 justify-center"
containerClassName="mt-6"
/>
Output:
Thanks for reading!
Remember, you can always add more custom components to your blog posts by editing the mdx-components.tsx
file. If you would like to add a new default MDX component to the Samwise template, please follow the Contribution Guidelines, open a pull request on the GitHub repository and I will be happy to review your request.
If you have any questions or feedback, please feel free to reach out to me using any of my social links on my personal website: patrickprunty.com, or via email at patrickprunty.business@gmail.com.
1. ^ Here is a reference to a footnote with a link to my website. You can return to where you left off in the blog post by clicking on the '^' character at the beginning of this footnote.
2. ^ Robert Frost's poem The Road Not Taken.