Now that this works,
let me come back to this content
and let's see how we could make this content
a bit more complex.
To be precise, let's see how we could add images
or code snippets to this content
because since we're building a blog
and we are developers,
we might be blogging about development topics
and that means that we also might wanna share code snippets
as part of our posts.
The great thing is that we can easily add such snippets
and also images to Markdown
but when it comes to rendering that,
we'll face certain challenges,
which we are going to solve together though.
Let's start with adding images to our Markdown content.
Attached, you find an updated
getting-started-with-nextjs Markdown file.
And you can replace your existing one with it.
It has the same metadata
but different content, just some dummy content
with a list, another title
and also with an image.
This is how you include a image in Markdown.
The problem is if you save that,
and you reload this page, the image is not showing up
because the path is incorrect.
Now, we could fix the path here
and change it to images/post
and then our Slack,
so getting-started-with-nextjs.
Make sure you have no typos.
And then also add that image,
which you also find attached.
You also need to do that.
Attached, you find a Next.js file-based routing
PNG dummy image,
which you can also add to your posts folder
in the images folder.
So to this specific posts folder there.
Now this path should be correct.
Oops, that should be posts though
but if you check for all typos,
it should be correct
and if you reload now, you should see the image here
but we have a couple of problems.
Clearly it's too big.
It's not styled.
Now, we could style it.
That wouldn't be a problem.
We could style it by simply adjusting the styles
in the post-content.module file.
We could target image elements here
and for example, give them a max-width of 200 pixels
or something like this
and now it would be smaller.
So that is a problem which we can solve.
I'll get rid of this though.
But that's something we could solve.
The bigger problem is that we're not taking advantage
of Next.js image optimizations here.
If we inspect this,
we see that it's a regular image element,
which is being rendered here.
And that is okay
but we give up on all the optimization potential.
For example, this is a huge image file, it's huge
and even if I change the size in CSS,
we still load this original file.
And that's definitely not what we want.
Lazy loading would also be nice to have
because the image is not even visible
when we start reading this post
and therefore, it would be great
if it is only loaded once we scroll there.
And that's all something next/image does for us
but not right now
because by default, react-markdown translates an image
in the Markdown file, so translates code like this
into a regular image tag
and that makes a lot of sense
because how would it know that you want
to have your own image transformation,
that you want to use a different component?
It doesn't know that but you can tell it.
And therefore, first of all, I'll get rid
of that path here and just stick to the image file name here
because we will now override
how react-markdown treats Markdown content like this,
how it treats images.
To override how certain elements are rendered,
we can give react-markdown a special prop,
the renderers prop.
Renderers wants an object
where you tell react-markdown
how certain elements should be rendered.
Now, you can't come up with your own elements here.
Instead, there are built-in elements
where you can override the default.
The react-markdown documentation is the place to go
for more details.
There you find detailed usage instructions.
You can also configure other things there
and there, you also get like a list
of the different elements it's able to render
and the identifier in renderers.
So for example, for an image,
it by default renders the image element
and we can target an image property
in our renderers object if we wanna override it.
So renderers are, let's say customRenderers
is an object.
That's what we should pass to renderers
if we wanna override some renderers.
And in this object we can now target identifiers
like image here.
Like this.
And set them as a property
or to be precise, as a method.
And react-markdown will call this method
if it finds a image in the Markdown content.
We can do this for other kinds of elements as well
as you see here.
So here I'm interested in an image.
If we find an image,
then I don't wanna use the default image element
but instead this method will be called.
In this method, we get data about the image which was found
and here we then should return our own JSX element.
So we could return the built-in image
but then we just do what react-markdown does anyways.
And we set the src and so on.
That's not what I'll do here.
Instead, I'll return the Next.js image element,
the Next.js image component,
which we therefore, of course, also need to import.
We import Image from next/image.
Like this.
So that's what we return here then
and then we set the src to something,
the alt text to something
and also width and height.
Like this.
Now, let's start with alt.
The image which we get here is actually an object
with the image data react-markdown got
from the Markdown text.
And the alt text, for example,
in Markdown is the text between those square brackets.
So that is already made available
by react-markdown and hence, we can set alt to image.alt.
The name, the URL of the image,
so the thing between the parentheses, this here,
is also made available by react-markdown.
It's also available on this image object
and it's the image.url.
But that's just a file name in our case.
We need a full path here
and for that, I'll create a template literal
where I point at images/posts/post.slug,
so the slug for this loaded post,
slash and then this URL,
this file name which was stored in the Markdown file.
So in this case, image.url.
So now that's that.
Now, for width and height,
it always comes down to how you present this to your styles
and so on.
Here a width of 600 and a height
of 300 pixels should work quite well.
Now with that, since we pass customRenderers to renderers,
this next image will be rendered instead of a default image.
If we save that,
and reload, that fails.
Actually because it's not image.url but image.src here, src.
My bad.
So image.src is the property on this image object,
which holds a pointer at the image we're using
and if we change this to image.src,
we now see our image rendered here
with the dimensions we gave it.
But if we open the dev tools,
we'll be greeted with a lot of warnings here.
The warning in the end tells us
that we're rendering a div inside of a paragraph
because the next/image element
is actually not just an image.
Instead, it is wrapped in a couple of divs,
which control its positioning and styling and so on.
And that's all rendered automatically by Next.
And that's then nested inside of a paragraph.
Because Markdown is translated to HTML
such that all content which is not a title
or a list or something like this
is treated as a paragraph, including images.
We can ignore this, it works.
But if we wanna get rid of that error,
of that warning, which it is,
we can also do that by not overriding the image
but by instead getting a bit more tricky.
Instead of only overriding images here
in our customRenderer,
we can actually have a look at all paragraphs,
which are being rendered.
So this now includes everything
which would be rendered as a paragraph,
including plain text, like this one or this one
but also images, which as we just saw,
are also rendered as paragraphs.
Then here we still get
that paragraph identified by react-markdown
and we can return our own JSX element.
And here the trick is that I only wanna override the default
if we actually find that a image
would be rendered inside of the paragraph
and I will leave all other kinds of paragraphs alone.
For this, we can extract the actual node
which would be rendered by react-markdown
from paragraph and then check if the node,
if the first child of that node,
if the type of that is image.
So if we are trying to render a image element
as a direct child of that paragraph.
If we do render an image inside of the paragraph,
I will override what react-markdown wants to render
and instead of rendering a paragraph with the image
inside of it, I then instead want to render just an image,
just my next image.
So for this, I'll then get my image
by accessing node.children and then the first child
and then I'll return a div actually
with some extra styling where I add a class of image.
That's simply a class I defined here in the CSS file.
And in there, I'll then render this next image.
So here I'll just repeat the code from up there,
grab that code, add it here like this.
And we're good.
Now we can get rid of this customRenderer
and I'll comment it out so that we still see it
but will not use it anymore
because we will now change image
by looking into paragraphs
and looking for images there
and then replacing the entire paragraph
with a div with the image.
Now, otherwise, if we don't make it into this if check,
I will return a regular paragraph with paragraph.children
inside of it.
So I'll basically render what react-markdown
would have rendered otherwise.
So I'll just use
that unchanged content it identified wrapped
in a regular paragraph.
If we do this and reload,
the image is broken simply
because here it's now actually not image.src but image.url.
That can be confusing but it is different here
when we're not handing just a image
but a paragraph that may contain an image.
Then the image, which we get from inside the paragraph
has the file name stored in a URL property
instead of src but with that it works.
And that's now the image presented on the screen.
Now rendered again with Next.js
as we can tell by the complex structure here
and the fact that different images are fetched
for different screen resolutions
and in the console if we reload,
we have no warnings.
And that's how we can override
how certain elements are rendered.
And the image was just one example
because another popular use case could be code snippets.
That's what we're going to do explore in the next step.