---
title: Next Intl Blog Template
description: Start your blog in multiple languages!
tags: [Next.js, next-intl, tailwindcss]
image: /portfolio/next-intl-blog-template/banner.png
imageCaption: Next Intl Blog Template banner
date: 2023-12-18
author: Juan Manzanero
rss: true
---

![Next Intl Blog Template banner](@/assets/portfolio/next-intl-blog-template/banner.png)
_Next Intl Blog Template banner_

[GitHub](https://github.com/juancmandev/next-intl-blog-template)

[Website](https://next-intl-blog-template.vercel.app/en)

## Overview

Recently I update this website, and as you may know, is an **English and Spanish
content website**.

I'm not using a translation plugin, instead I write every work in both English
and Spanish.

Thanks to Next.js and [next-intl](https://next-intl-docs.vercel.app/) I can
achieve this, rendering routes for each language in the website, accessing a
dictionary that contains the content translated by me.

For the .mdx files, I created a directory for each language, and inside of those
directories it contains the content in both languages too.

## How to use

This template is an extension of
[next-intl](https://next-intl-docs.vercel.app/), chek the
[getting started](https://next-intl-docs.vercel.app/docs/getting-started) to
learn the basics, the purpouse of the template is to create a simple layout for
future customization.

### Add or remove locales

You can add or remove locales in the `src/lang/locales.ts` file.

```ts title="src/lang/locales.ts"
export type locales = 'en' | 'es';

export const localesList: locales[] = ['en', 'es'];
```

Just add or remove a locale from the `locales` const, and add or remove it from
the list.

The first item in the `localesList` must be the default locale.

The list is used for static generation of the routes in
`src/app/[locale]/layout.tsx`.

```ts title="src/app/[locale]/layout.tsx"
import { localesList } from '@/lang/locales';

export function generateStaticParams() {
  return localesList.map((locale) => ({ locale }));
}
```

Remember to update the matcher in `src/middleware.ts`.

```ts title="src/middleware.ts"
//...

export const config = {
  matcher: ['/', '/(en|es)/:path*'],
};
```

And of course, update your `src/lang/[locale].json` files.

### Content creation

Use `src/content/[locale]` for create content, in the `/[locale]/` directory
ceate the directory for each purpouse, for example: `/[locale]/blog`.

Inside create the .mdx file with an unique name, the name will be used as the
slug for create the static page for that post.

For create a blog section, you'll use the _getAllContent_ function in your
route, for example: `src/app/[locale]/blog/[slug]/page.tsx`.

```tsx title="src/app/[locale]/blog/[slug]/page.tsx"
import { Mdx } from '@/components';
import { TParamsLocale, TPage, TSlugLang } from '@/types';
import { Metadata } from 'next';
import { getAllContent, getContent } from '@/utils/getContent';

export async function generateStaticParams(
  props: TParamsLocale,
): Promise<TSlugLang[]> {
  const blogs = await getAllContent(props.params.locale, 'blog');

  if (!blogs) return [];

  return blogs.map((blog) => ({
    slug: blog.slug,
    locale: props.params.locale,
  }));
}

//...
```

This will create each static page for each blog post.

You can get the metadata of the .mdx file too.

```tsx title="src/app/[locale]/blog/[slug]/page.tsx"
//...

export async function generateMetadata(props: TPage): Promise<Metadata> {
  const blog = await getContent(props.params.locale, 'blog', props.params.slug);

  if (!blog) return {};

  return {
    title: blog.title,
    //...
  };
}

//...
```

Then, render the content using the _Mdx_ component.

```tsx title="src/app/[locale]/blog/[slug]/page.tsx"
//...

export default async function Page(props: TPage) {
  const post = await getContent(props.params.locale, 'blog', props.params.slug);

  if (!post) return null;

  return <Mdx code={post.body.code} />;
}
```

[You can fork this template here](https://github.com/juancmandev/next-intl-blog-template)
