Frontend Development 13 min read

SSR and SEO Optimization Practices for Next.js 13 with Tailwind CSS

The guide shows how to use Next.js 13’s server‑side rendering with TypeScript and Tailwind CSS to generate fully rendered HTML for better SEO, recommending direct output of key content, native anchor tags for navigation, proper TDK, robots.txt, sitemap.xml, async components, CDN asset prefixes, optimized Tailwind utilities, and careful image handling.

Ximalaya Technology Team
Ximalaya Technology Team
Ximalaya Technology Team
SSR and SEO Optimization Practices for Next.js 13 with Tailwind CSS

The project is built with Next.js 13.4, Tailwind CSS and TypeScript, using Server‑Side Rendering (SSR) to improve SEO, enable better search‑engine crawling, and increase natural ranking for the target app.

In SSR the term "direct output" (直出) refers to generating the complete HTML on the server and sending it to the client, so the page can be displayed immediately. This differs from Client‑Side Rendering (CSR), where the browser builds the UI with JavaScript after the initial load.

SEO recommendations :

Important content (e.g., chapter lists on a novel page) should be directly output; less important sections can be loaded asynchronously.

Use native <a> tags for navigation instead of router.push to allow crawlers to follow links.

Design a clear link structure with internal and external links, and ensure each page has its own TDK (title, description, keywords) and semantic HTML headings.

Add rel="nofollow" to <a> tags for pages that should not be indexed.

Place a robots.txt file at the domain root to control what crawlers may access.

Provide a sitemap.xml that lists URLs, last‑modified dates, change frequency and priority.

Example of a bad router push and a good <a> usage:

// bad
import { useRouter } from "next/router";
const router = useRouter();
router.push({ pathname, query }, asPath, { locale: nextLocale });

// good
<details open>
  <summary><b>Examples</b></summary>
  <ul>
    <li><a href="https://github.com/nextauthjs/next-auth-example">next-auth-example</a></li>
  </ul>
</details>

Performance tips :

Reduce DOM depth; use the minimal number of wrapper elements.

Only return fields that are needed for the front‑end when assembling the HTML on the server.

Load non‑essential components asynchronously with next/dynamic or a custom NoSSR wrapper.

import dynamic from "next/dynamic";
import SelfAdaption from "../base/SelfAdaption";
const DynamicHeader = dynamic(() => import("../base/BackTop"), { ssr: false });
import { useEffect, useState } from "react";
export function NoSSR({ children }) {
  const { children } = props;
  const [isMounted, setIsMounted] = useState(false);
  useEffect(() => { setIsMounted(true); }, []);
  if (!isMounted) return null;
  return <div>{children}</div>;
}

Configure a CDN asset prefix in next.config.js :

const isProd = process.env.ENVIRONMENT === "product"; // prod
const isTest = process.env.ENVIRONMENT === "test"; // test
const mode = process.env.NODE_ENV === "production";
module.exports = {
  assetPrefix: mode && isProd
    ? "https://xx.cdn.com/last/"
    : mode && isTest
    ? "https://xx.test.cdn.com/last/"
    : "",
};

Define output directories in package.json for CI/CD:

"yxpm": {
  "output": ["public", "_next"]
}

Use the Head component with a key attribute to avoid duplicate meta tags:

import Head from "next/head";
function IndexPage() {
  return (
    <div>
      <Head>
        <title>My page title</title>
        <meta property="og:title" content="My page title" key="title" />
      </Head>
      <Head>
        <meta property="og:title" content="My new title" key="title" />
      </Head>
      <p>Hello world!</p>
    </div>
  );
}
export default IndexPage;

Tailwind CSS details:

Utility‑first framework that can purge unused classes during build.

Create reusable components with @apply : <!-- Before extracting a custom class --> <button class="py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75">Save changes</button> <!-- After extracting a custom class --> <button class="btn-primary">Save changes</button> // styles/global.css @tailwind base; @tailwind components; @tailwind utilities; @layer components { .btn-primary { @apply py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75; } }

Access Tailwind config values with theme() : div { border-top: 1px solid theme('color.gray.200'); }

Extend the config with custom colors: // tailwind.config.js module.exports = { theme: { extend: { textColor: { primaryOne: "#111111", primaryThree: "#333333", primarySix: "#666666", primaryNine: "#999999", }, }, }, }; <span class="text-primaryOne">主色</span> <span class="text-primaryThree">常规色</span> <span class="text-primarySix">次要色</span>

Additional notes:

Image lazy loading can hurt SEO; consider loading images directly when SEO is a priority.

Some Tailwind styles break in IE10; a polyfill may be required.

Disable double rendering of _app.tsx by setting reactStrictMode: false in next.config.js .

Configure cross‑origin rewrites in next.config.js for local development. async rewrites() { return [{ source: "/mobile/app/latest", destination: `http://xx.com/mobile/app/latest` }]; }

Install analytics SDK via npm instead of CDN to avoid unpredictable errors: npm install @xmly/xmrep

Handle 301/302 redirects and 404 pages with getServerSideProps : export const getServerSideProps: GetServerSideProps = async ({ params = {}, res }) => { const { keyword = "" } = params; if (keyword) { const kw = decodeURIComponent(keyword); const data = await getRxx({ kw, pageNo: 1, pageSize: 30 }); if (data && data.docs && data.docs.length) { return { props: { ...data, keyword: kw } }; } else { return { notFound: true }; } } else { return { redirect: { destination: `/`, statusCode: 302 } }; } };

frontendperformanceSSRWeb DevelopmentNext.jsseoTailwind CSS
Ximalaya Technology Team
Written by

Ximalaya Technology Team

Official account of Ximalaya's technology team, sharing distilled technical experience and insights to grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.