Meta tags

According to your settings in the app_settings.ts file, shipit will generate the important meta tags for your app. You can check how this works by going to src/app/layout.tsx. You will find this code:

src/app/layout.tsx
export const metadata: Metadata = {
  title: SEO_TITLE,
  description: SEO_DESCRIPTION,
  metadataBase: new URL(APP_DOMAIN),
  alternates: {
    canonical: new URL("/", APP_DOMAIN),
  },
  openGraph: {
    type: "website",
    locale: APP_LANG,
    title: SEO_TITLE,
    description: SEO_DESCRIPTION,
    siteName: APP_NAME,
    url: new URL(APP_DOMAIN),
    images: [
      {
        url: SEO_IMAGE ?? "/api/og",
      },
    ],
  },
  twitter: {
    card: "summary_large_image",
    images: [
      {
        url: SEO_IMAGE ?? "/api/og",
      },
    ],
    title: SEO_TITLE,
  },
};

This will adapt and work with every page in your app without further configuration.

Only the page of a blog post, have a different configuration, since we want to show meta information about the post. If you go to src/app/blog/[slug].tsx, you will find this code:

src/app/blog/[slug].tsx

export async function generateMetadata(
  { params, searchParams }: MetaDataProps,
  parent: ResolvingMetadata
): Promise<Metadata> {
  const postContent = await getPostContent(`${params.slug}.mdx`);
  const post = await getPostMDX(postContent);
  const frontMatter = post.frontmatter as unknown as BlogPostFrontmatter;

  return {
    title: frontMatter.title,
    description: frontMatter.description,
    openGraph: {
      type: "article",
      images: [
        {
          url: frontMatter.cover_image,
        }
      ],


    },
    twitter: {
      images: [{
        url: frontMatter.cover_image

      }]
    }

  }
}

This will generate the neccesary meta tags for a each blog post.

OG Image

If you look closely to the Metadata object in the layout.tsx file, you will see this code:

src/app/layout.tsx
 images: [
      {
        url: SEO_IMAGE ?? "/api/og",
      },
    ],

If you don’t specify a SEO_IMAGE url in your app_settings.ts file, shipit will generate a default image for you. This image is generated by the api/og endpoint, which is a serverless function that uses next/og package to generate the image. If you wish you can customize this image by going to the src/api/og/route.tsx file. You will find the following code:

src/api/og/route.tsx
import { ImageResponse } from "next/og";
import { APP_DESCRIPTION, APP_NAME } from "@/app_settings";
export const runtime = "edge";

export async function GET() {
  return new ImageResponse(
    (
      <div
        style={{
          height: "100%",
          width: "100%",
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          justifyContent: "center",
          backgroundColor: "white",
          textAlign: "center",
        }}
      >
        <div tw=" flex flex-col text-center items-center ">
          <span tw="text-4xl font-bold">{APP_NAME}</span>
          <span tw="mt-4 text-lg">{APP_DESCRIPTION}</span>
        </div>
      </div>
    ),
    {
      width: 1200,
      height: 630,
    }
  );
}

You can use tailwind to style the image as you wish.

Sitemap

shipit generates a sitemap for you, so you don’t have to worry about it. But if you are curious about how it works, you can go to the src/app/sitemap.ts file. You will find this code:

src/app/sitemap.ts
import { APP_DOMAIN, APP_ROUTES } from "@/app_settings";
import { getPostsList } from "@/server/helpers/blog/get-post-list";
import { getPostDate } from "@/server/helpers/blog/get-post-date";
export default async function sitemap() {
  const routes = APP_ROUTES.filter(route => route.visibleBy === 'all').map((route) => ({
    url: `${APP_DOMAIN}${route.path}`,
    lastModified: new Date().toISOString(),
  }));

  const blogPosts = await getPostsList();

  const postsRoutes = await Promise.all(
    blogPosts.map(async (post) => {
      const { modifiedAt } = await getPostDate({ fileName: post });
      return {
        url: `${APP_DOMAIN}/blog/${post}`,
        lastModified: modifiedAt.toLocaleDateString()
      };
    })
  );


  return [...routes, ...postsRoutes];
}

The first group of routes are the ones that are defined in the app_settings.ts file. That are the routes of your application. It will filter by the ones that are visible to all users, ince it doesn’t make sense to include in the sitemap the routes that are only visible to authenticated or suscribed users.

The second group of routes will add all your blog posts routes to the sitemap.