Adding meta data to a remix app for SEO

16/02/2023

Intro and Basic Setup

When building this app, I wanted to make sure that I can rank in search engine. When somebody googles my name for instance, I wanted my page to appear as the first result, or at least very near the top. This is one of the reasons I have decided to use remix in the first place - as it renders server side and that will allow search engines to crawl it more easily. The next step was to actually add the required meta tags to each page.

At the time of writing, my app had some static pages, as well as "blog" pages and "project" pages. I also wanted to include a fallback SEO meta tag that will display on all pages that don't have a specific one defined. To start, I have added the following piece of code to the root.tsx file of my project, above the default function export:

export const meta: MetaFunction = () => ({
  charset: "utf-8",
  title: "Luka Lazic | Typescript Development | ll-tech",
  description: "This is my personal website where I showcase my projects and write about web development. I like to use React, Remix, Tailwind, Prisma and more!",
  viewport: "width=device-width,initial-scale=1",
  keywords: "luka lazic, react, c#, dotnet, typescript, remix, tailwind, entity framework"
});

Bear in mind that in the same file, in the return, we still need to have the Meta tag which should be there by default, unless it's removed for some reason. This is about how it should look:

export default function App() {
  return (
    <html lang="en">
      <head>
        <Meta /> // notice the Meta tag here
...

Now, after we open up any page of the app and check it with a SERP checker tool, we should see that the meta tags are displayed on all pages. This is much better than having nothing, however we should really strive to provide different data for all our different pages. We can separate the pages into two types, one type are the static ones (here it would be the home page, the blog page, project page etc.) and the other type are the dynamic ones, like all the different pages for blog posts and projects that get data from the database. I will explain how to add meta to both of them.

Static Pages

For static pages, the process is quite simple. On each of the pages we will just add the same meta function export as in our root.tsx file as explained above. Remix will be clever enough to realize this is the meta it needs to use. These functions need to be in the routes folder. For example, on this app, to add meta data I have added the function to routes/blog/index.tsx. The code is pretty much the same as the one above, just with slightly different meta. As I want to keep some of the meta data that will be shared among all pages, I will import it and spread it and just overwrite what I need. That way, once we update some of the basic meta data in the root, it will be updated everywhere. Here is the code:

...
import { meta as rootMeta } from "../../root";

export const meta: MetaFunction = () => ({
  ...rootMeta,
  title: "Blog | Luka Lazic | ll-tech",
  description: "My personal blog where I write about web development. I like to use React, C#, Tailwind, Prisma and more!",
});

...

Dynamic Pages

Dynamic pages are a very broad term, but in this case I refer to pages that have data pulled from an endpoint or a database. The blog pages in this app are an example of that, as well as the project pages. In this case, I have a file called $postSlug.tsx that is located in routes/blog/ where I return a single blog post fetched from the database. I fetch all my data using a loader function from where I get all the post info and validate it:

type LoaderData = { post: Post; html: string };

export const loader: LoaderFunction = async ({ params }) => {
  invariant(params.postSlug, `params.slug is required`);
  const post = await getPost(params.postSlug);
  invariant(post, `Post not found: ${params.postSlug}`);
  const html = marked(post.markdown);
  return json<LoaderData>({ post, html });
};

Your loader function might blook completely different and that is fine, the important part is the post variable fetched from LoaderData, which in my case holds everything that we need to create our meta.

The meta function looks like this:

export const meta: MetaFunction = ({ data }) => {
  return {
    ...rootMeta,
    title: data.post.seo_title + " | Luka Lazic l-l tech",
    description: data.post.seo_description,
  };
};

It keeps all the information from our root page, same as in the example for the static pages, but it also takes the seo title and description from whatever we pulled from the backend, so every blog post has it's own specific title and description. You can repeat this process for all types of pages that you have in your app!

After this, the next step to further improving the seo of our Remix app would be to also add a sitemap as well as robots.txt, which I will cover in a future article and then post a link here.

Share this post :