GuidesSSR Patterns

SSR Patterns

This guide covers server-side rendering patterns used in the codebase.

Overview

Next.js offers three rendering modes:

ModeWhen Code RunsUse When
SSR (getServerSideProps)Server, every requestDynamic data, SEO-critical
SSG (getStaticProps)Server, build timeStatic content
CSR (client-side)Browser onlyInteractive features

The Rendering Module

SSR logic lives in rendering/server/:

rendering/
├── server/
│   ├── helpers.ts     # Shared utilities
│   ├── stories.ts     # Story-specific SSR
│   └── index.ts       # Barrel exports
└── index.ts

Using SSR Helpers

For Story Pages

// pages/stories/[slug].tsx
import { getStorySSR, StorySSRProps } from '@/rendering/server';
 
export default function StoryPage({ story, error, ogImage, excerpt }: StorySSRProps) {
  if (error) return <ErrorState error={error} />;
  return <StoryContent story={story} />;
}
 
// Delegate SSR to the rendering module
export const getServerSideProps = getStorySSR;

Creating New SSR Handlers

To add SSR for a new page type:

// rendering/server/projects.ts
import { GetServerSideProps } from 'next';
import { createErrorProps } from './helpers';
 
export interface ProjectSSRProps {
  project: Project | null;
  error?: string;
}
 
export const getProjectSSR: GetServerSideProps<ProjectSSRProps> = async (context) => {
  const { slug } = context.params || {};
 
  if (!slug || typeof slug !== 'string') {
    return { props: { project: null, error: 'Project not found' } };
  }
 
  try {
    const apiUrl = `${process.env.BACKEND_URL}/projects/slug/${slug}`;
    const response = await fetch(apiUrl);
 
    if (!response.ok) {
      return { props: { project: null, error: 'Project not found' } };
    }
 
    const project = await response.json();
    return { props: { project } };
  } catch (error) {
    return { props: { project: null, error: 'Failed to load project' } };
  }
};

Then export from the barrel:

// rendering/server/index.ts
export { getProjectSSR, type ProjectSSRProps } from './projects';

Helper Functions

createStoryErrorProps

Creates standardized error props with fallback OG images:

import { createStoryErrorProps } from '@/rendering/server';
 
// Returns { story: null, error: 'message', ogImage: '...', excerpt: '...' }
const errorProps = createStoryErrorProps('Story not found');

processStoryDataSSR

Extracts OG image and excerpt from story content:

import { processStoryDataSSR } from '@/rendering/server';
 
const { ogImage, excerpt } = await processStoryDataSSR(story);

Best Practices

Keep page files thin

Page files should only handle routing and SSR delegation:

// Good - thin page file
import { getStorySSR, StorySSRProps } from '@/rendering/server';
import { StoryPage } from '@/modules/stories';
 
export default function Page(props: StorySSRProps) {
  return <StoryPage {...props} />;
}
 
export const getServerSideProps = getStorySSR;

Use environment variables for backend URLs

const apiUrl = process.env.BACKEND_URL || process.env.NEXT_PUBLIC_API_URL;
  • BACKEND_URL - Server-side only, more secure
  • NEXT_PUBLIC_API_URL - Available client-side, fallback

Handle errors gracefully

Always return valid props, even on error:

try {
  // fetch data
} catch (error) {
  return { props: { data: null, error: 'Failed to load' } };
}

SEO considerations

For SEO-critical pages, include OG meta in SSR props:

return {
  props: {
    story,
    ogImage: extractedImage || defaultImage,
    excerpt: firstParagraph,
  }
};

When to Use Each Pattern

Use SSR (getServerSideProps) when:

  • Page needs fresh data on every request
  • SEO is critical (story pages, public content)
  • Data changes frequently

Use SSG (getStaticProps) when:

  • Content rarely changes
  • Can be rebuilt periodically (revalidate)
  • Example: home page story list

Use CSR (client-side) when:

  • Data is user-specific
  • Real-time updates needed
  • Example: engagement (reactions, comments)