Creating Blog Tutorial Using Next.JS 15 & TailwindCSS 4.0 (Part #2)



This content originally appeared on DEV Community and was authored by gerry leo nugroho

Welcome Back, Fellow Builders! After laying the foundation in our project initialization, we’re now ready to shape the heart of our blog with configuration files. This step is where the magic happens—turning our raw tech stack into a finely tuned machine. We’ll configure Next.js 15.1.7 to handle MDX content and modern typography, set up Tailwind CSS 4.x.x for sleek styling.

These files are:

  • next.config.ts
  • postcss.config.mjs
  • tailwind.config.js

And one file to import the default tailwind to out styling.

  • src/app/globals.css

So let’s dive in with precision and flair.

Step 1: Next.js Configuration with next.config.ts

First up is next.config.ts, the command center for our Next.js app. This file lets us customize how Next.js compiles, bundles, and serves our blog, and we’ll spice it up with MDX support, modern features, and a custom Webpack config for advanced use cases. Based on our package.json, we’re running Next.js 15.1.7, so let’s ensure our setup leverages its latest goodies—like the Rust-based MDX compiler and a touch of Webpack magic.

// next.config.ts
import mdx from '@next/mdx';
import UnpluginFonts from 'unplugin-fonts/webpack';

// Enable MDX support with Next.js
const withMDX = mdx({
  extension: /\.mdx?$/, // Support both .md and .mdx files
  options: {
    remarkPlugins: [], // Add remark plugins here if needed
    rehypePlugins: []  // Add rehype plugins here if needed
  }
});

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Specify file extensions Next.js should recognize
  pageExtensions: ['ts', 'tsx', 'md', 'mdx'],

  // Experimental features for Next.js 15.x.x
  experimental: {
    mdxRs: true,         // Use Rust-based MDX compiler for faster builds
    reactCompiler: false  // Integrate React 19 compiler for optimized rendering, but we put false value for the time being.
  },

  // Custom Webpack configuration for advanced use cases
  webpack: (config, { isServer }) => {
    // Add unplugin-fonts to Webpack for Google Fonts integration
    config.plugins.push(
      UnpluginFonts({
        google: {
          families: [
            'Inter:400,700',    // Sleek sans-serif for general text
            'Merriweather:300,400' // Elegant serif for headings
          ]
        }
      })
    );

    // Example: Add custom loader for specific file types (e.g., SVGs)
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack'] // Requires `npm install @svgr/webpack` if used
    });

    // Optimize server-side vs client-side builds
    if (!isServer) {
      config.optimization.splitChunks = {
        chunks: 'all', // Enable code splitting for better caching
      };
    }

    return config;
  }
};

// Export the configuration with MDX support
export default withMDX(nextConfig);

What’s Happening Here?

  • MDX Integration: The @next/mdx plugin lets us write blog posts in MDX, blending Markdown with JSX. We’ve set it to recognize both .md and .mdx files for flexibility.
  • Experimental Features: mdxRs swaps the default MDX compiler for a Rust-based one—faster builds, happier devs. reactCompiler taps into Next.js 15’s integration with React 19’s compiler, optimizing our components automatically.
  • Custom Webpack: We’ve added unplugin-fonts/webpack to load Google Fonts efficiently. The Webpack config also includes a placeholder for SVG handling (install @svgr/webpack if needed) and a server-side optimization tweak—demonstrating how to extend Next.js for advanced scenarios.
  • TypeScript: Written in .ts for type safety, matching your TypeScript-enabled project.

This file ensures our blog is fast, font-ready, and future-proof.

Step 2: PostCSS Configuration with postcss.config.mjs

Next, we tackle postcss.config.mjs. Since we’re using Tailwind CSS 4.0.8 with @tailwindcss/postcss, PostCSS is our CSS processor of choice. This config ties Tailwind into our build pipeline, ensuring our styles are processed efficiently. If the file is not available already, please create one in the root of your project folder, give it a name postcss.config.mjs and fill-up the value as per the following:

Here’s the setup:

// postcss.config.mjs

/** @type {import('postcss-load-config').Config} */
const config = {
  plugins: {
    '@tailwindcss/postcss': {}, // Tailwind CSS 4 integration
    // autoprefixer: {}, // Adds vendor prefixes for broader browser support
  },
};

export default config;

Why This Matters

  • Tailwind Plugin: The @tailwindcss/postcss entry hooks Tailwind into PostCSS, enabling its utility-first magic. Since we installed it manually (npm install tailwindcss@4.x.x @tailwindcss/postcss postcss), this ensures we’re running the latest version.
  • Autoprefixer: This bonus plugin adds prefixes (e.g., -webkit-) to CSS properties, ensuring our blog looks flawless across browsers like Chrome, Firefox, and Safari.

Place this file in your blog/ root alongside next.config.mjs. It’s a small but mighty piece of the puzzle.

Step 3: Tailwind CSS Configuration: Styling with Elegance

Now, let’s configure Tailwind CSS in tailwind.config.js. This file defines our design system—fonts, colors, and plugins—while embracing Tailwind 4.x.x’s CSS-first approach. Here’s the detailed setup:

// tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
    // Specify where Tailwind should look for classes
    content: [
        "./src/**/*.{ts,tsx,md,mdx}", // Default content directory
        "./src/pages/**/*.{ts,tsx,md,mdx}", // Legacy pages directory (if used)
        "./src/components/**/*.{ts,tsx}", // Reusable components
        "./src/app/**/*.{ts,tsx}", // App Router files
    ],

    theme: {
        extend: {
            // Custom font families from Google Fonts
            fontFamily: {
                sans: ["Inter", "sans-serif"], // Clean, modern sans-serif
                serif: ["Merriweather", "serif"], // Elegant serif for prose
            },

            // Gradient colors for a sleek, modern design
            colors: {
                gradientStart: "#4f46e5", // Indigo for vibrant gradients
                gradientEnd: "#9333ea", // Purple for a trendy finish
            },

            // Customize typography plugin for blog content
            typography: (theme) => ({
                DEFAULT: {
                    css: {
                        color: theme("colors.gray.800"), // Readable text in light mode
                        "h1, h2, h3": {
                            fontFamily: theme("fontFamily.serif"), // Serif headings for flair
                        },
                    },
                },
                dark: {
                    css: {
                        color: theme("colors.gray.200"), // Crisp text in dark mode
                    },
                },
            }),
        },
    },

    // Enable Tailwind plugins
    plugins: [
        require("@tailwindcss/typography"), // Beautiful prose styling
    ],

    // Use class-based dark mode
    darkMode: "class",
};

Breaking It Down

  • Content: We tell Tailwind where to look for classes—our src/ directory covers components, App Router pages, and any legacy pages/. This ensures purging works efficiently in production.
  • Theme Extensions:
    • Fonts: We sync with unplugin-fonts by defining Inter for body text and Merriweather for headings—modern and readable.
    • Colors: Custom gradient colors (gradientStart and gradientEnd) give our blog a trendy, sleek vibe.
    • Typography: The @tailwindcss/typography plugin gets a tweak—light and dark mode text colors, plus serif headings for a polished look.
  • Plugins: Adding @tailwindcss/typography ensures our MDX blog posts render beautifully with sensible defaults.
  • Dark Mode: The class strategy integrates with next-themes for seamless light/dark switching.

Save this as tailwind.config.js in your blog/ root. It’s our design foundation.

Step 4: Global CSS: Setting the Stage with Tailwind

Finally, let’s craft src/globals.css to apply Tailwind’s base styles and define custom variables for theming. This file lives in src/styles/ (create the folder if it’s not there yet):

/* src/globals.css */
@tailwind base; /* Reset and base styles */
@tailwind components; /* Custom components (if needed later) */
@tailwind utilities; /* Utility classes */

/* Custom CSS variables for theming */
@layer base {
  :root {
    --bg-color: #ffffff; /* Light mode background */
    --text-color: #1f2937; /* Light mode text */
    --card-bg: #f9fafb; /* Light mode card background */
  }
  .dark {
    --bg-color: #1f2937; /* Dark mode background */
    --text-color: #d1d5db; /* Dark mode text */
    --card-bg: #374151; /* Dark mode card background */
  }
  /* Apply variables globally */
  body {
    @apply bg-[var(--bg-color)] text-[var(--text-color)]; /* Dynamic theming */
    font-family: 'Inter', sans-serif; /* Default font */
  }
}

What’s Going On?

  • Tailwind Directives: @tailwind base resets browser styles, @tailwind components is a placeholder for custom components (we’ll use it later), and @tailwind utilities brings in all those handy classes.
  • CSS Variables: Inside @layer base, we define --bg-color, --text-color, and --card-bg for light and dark modes. The .dark class toggles these values when next-themes applies the dark class to <html>.
  • Body Styling: Using @apply, we dynamically set the background and text colors with our variables, ensuring a cohesive look. The font-family defaults to Inter for consistency.

This file ties Tailwind to our app—create src/styles/ and save it there.

Step 5: Testing the Configuration

To verify, update src/app/page.tsx to use our styles:

export default function Home() {
  return (
    <div className="to-light-blue-500 mt-20 bg-gradient-to-r from-cyan-400 p-20">
      <h1 className="bg-clip-text text-center font-mono text-9xl text-cyan-700">
        Welcome to My Blog
      </h1>
    </div>
  );
}

Run the following command in your terminal

npm run dev --turbopack

Next fire-up your browser of choice, and visit [http://localhost:3000](http://localhost:3000). You should see a gradient-styled heading in Mono-font—proof our configs are humming along.

Gemika Haziq Nugroho - Gerry Leo Nugroho's Blog

Step 6: Wrapping Up Configuration

There you have it! We’ve configured Next.js 15.1.7 to handle MDX and fonts, set up PostCSS for Tailwind CSS 4.x.x, defined a sleek design system, and applied global styles—all tailored to our project requirements. These files are the backbone of our blog, ensuring it’s fast, stylish, and feature-ready. Next, we’ll venture more for the structure of our project folders—I’ll see you there soon!


This content originally appeared on DEV Community and was authored by gerry leo nugroho