Adding a blog to your Dash App

Hello everyone,

Today, I will be covering how to add a simple blog section to your Dash site using the new Multi-Page Apps feature in Dash 2.5.

If you want any of the code shown below, feel free to checkout the Github Repository:
Dash Blog Page

The structure of the application follows the Structuring a large Dash application - best practices to follow guide I wrote. However, you should be able to easily add this code to any of your own applications which do not follow this structure.

Blog

Each article is created based on markdown files within the /src/pages/blog/articles directory.
For any of the code shown below, please see /src/pages/blog/layout.py and /src/pages/home.py pages.

Example article

The below is a sample article, found in /src/pages/blog/articles/2022-01-01-january-blog.md.

---
permalink: /blog/january
title: "January Blog"
author: "John Doe"
tab_title: "January Updates"
description: "This is an excerpt of the January article."
image: /assets/blog/woods.png
date: 2022-01-01
---

# Test

Simple write the blog post with Markdown here.

The metadata about each article is set using FrontMatter, the variables defined between the --- components.
Use these variables to set specific information about your application.
When creating each page, we will read the contents of the FrontMatter to set page specific content (such as link, title, description, and image).

Creating the article

Each article is added to the page registry by iterating over the contents of the article directory. We do this within /src/pages/blog/layout.py

cwd = os.getcwd()
file_path_in = os.path.join(cwd, 'pages', 'blog', 'articles')
article_files = os.listdir(file_path_in)
for article_name in article_files:
    # read in contents of a given article
    article_path = os.path.join(file_path_in, article_name)
    with open(article_path, 'r', encoding='utf-8') as f:
        post = frontmatter.load(f)

    publish_date = post.get('date')

    # skip articles that aren't published yet or are the sample files
    if publish_date > date.today() or article_name.startswith('sample'):
        continue

    # format image for proper sharing
    sharing_image = post.get('image').lstrip('/assets/')

    # register the specific page for the article
    dash.register_page(
        post.get("title"),
        title=post.get("tab_title"),
        description=post.get("description"),
        image=sharing_image,
        path=post.get("permalink"),
        layout=create_article_page(post)
    )
    # create the article card and add it to the list of cards
    # this is used for displaying a group of cards in another page
    # see the Linking to Articles section for more information
    article_cards.append(create_article_card(post))

The article is then input into the page layout which defines the title, author, and article contents. If you want to add any specific elements to all your articles, change this method.

def create_article_page(post):
    '''Create the layout of the article page'''
    date_str = post.get('date').strftime(date_format)

    image = post.get("image")
    layout = html.Div(
        dbc.Row(
            dbc.Col(
                [
                    html.Img(
                        src=f'{image}',
                        className='w-100'
                    ),
                    html.H1(f'{post.get("title")}'),
                    html.Hr(),
                    html.P(f'{post.get("author")} - {date_str}'),
                    html.Hr(),
                    dcc.Markdown(
                        post.content,
                        className='markdown'
                    )
                ],
                lg=10,
                xl=8
            )
        ),
        className='mb-4'
    )
    return layout

Your articles will look like the following:

Linking to articles

You’ll likely want to include links to your article on various other pages.
While iterating over each page, we can also feed the article contents into a card creator.

def create_article_card(post):
    '''Create clickable card to navigate to the article'''
    date_str = post.get('date').strftime(date_format)
    card = dbc.Card(
        html.A(
            [
                dbc.CardImg(src=f'{post.get("image")}', top=True),
                dbc.CardBody(
                    [
                        html.H4(
                            f'{post.get("title")}',
                            className='card-title'
                        ),
                        html.P(
                            f'{post.get("description")}',
                            className='card-text'
                        )
                    ]
                ),
                dbc.CardFooter(
                    f'{post.get("author")} - {date_str}',
                    class_name='mb-0'
                )
            ],
            href=post.get('permalink'),
            className='text-decoration-none text-body h-100 d-flex flex-column align-items-stretch'
        ),
        class_name='article-card h-100'
    )
    return card

The resulting cards will be used to populate the blog page, shown below.

Conclusion

Adding a blog to your site can help increase traffic and provide a place to talk about the analysis or future direction of your Dash Application. Let me know in the comments if you have any questions. I hope this helps!

6 Likes

What a detailed tutorial post. Thank you for writing this, @raptorbrad . I know about 3 people off the top of my head that are interested in adding blogs to their web app. And now with this article, that process should become a lot easier for them.

That said, if someone would like to deploy this app to the web, would they need to change anything in the folder structure or inside each files before deployment?

I believe for this they need to make sure they are running the application from inside the src directory. This is because of how the code looks for the articles directory. If someone wants to fix this and create a pull request, I will happily review and get it merged in!

1 Like

Hello @raptorbrad
This is awesome and I’m using it on my multipage webapp.
I am trying this with md files including pictures inside. I’m not sure whatthe right syntax is but I generate the .md using the Writage pliugin for Word. When including an image inside the file, it cretes a subfolder “media” where the article is, with png files, and then the md looks like that:


permalink: /blog/january
title: “January Blog”
author: “John Doe”
tab_title: “January Updates”
description: “This is an excerpt of the January article.”
image: /assets/blog/woods.png
date: 2022-01-01

Test

Simple write the blog post with Markdown here.

This is an image

(media/0a28e290f7d83403741a05ee5f2d39bd.png)

with the (…) part preceded by ! then square brackets open close
(sorry I need to type this, otherwise it doesn’t show in this post!)

That png file is not recognized by the dcc.Markdown() component as it displays a placeholder
image
I suspect the dcc.Markdown doesn’t handle base64 conversion of images, so can you think of a way to parse and diddle the post.content to have the embedded images understood?

Please let me know if there should be a better place to ask this.

Yes, adding images is easily doable.

Just include the path to the appropriate assets folder
![Image title](/assets/path/to/image.png)

1 Like

Yes! As you point out the key is to put the images folder inside /assets/ then it works like a charm!
Thank you very much

2 Likes

another question on embedded images inside the markdown: if they are wider than the container they will overlap. Is there an easy way to ensure they are resized to the max width of the container? I’ve looked around nd could only find the version with css sheet ensuring a fixed pixel width resizing. Even then that’s not ideal, as I’d like to be able to control this within the python code instead of separately.

Yes, but you do need a CSS file to accomplish this. Resize SVG in markdown covers how to do this by specifying pixel size; however, you can change the width to be 100% instead like so:

img[src*="#article"] {
    width: 100%;
}

Then your image will be ![Image title](/assets/path/to/image.png#article)

2 Likes

Exactly what I needed, and so easy! As I didn’t want to manually modify my .md files, I just used post.content.replace(“.png)”, “.png#article)”)
Thank you so much!

Awesome write up…

As an extension of the blog idea, what if you want a search bar to look for a specific blog post within your site?

Any ideas on how one may want to tackle that feature?

1 Like

That sounds like an excellent feature!

You could easily add a search bar to the main blog page. Then register a callback that will change which cards are displayed based on the current input.

If you are up to the task, feel free to try and implement this, then create a pull request. Otherwise, I’ll hack away once I have some time later this week.

1 Like

I’ll give it a shot. See if I can get something going. Thanks for the feedback