<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Server side rendering - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Server side rendering - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 27 Jun 2026 16:34:45 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/server-side-rendering/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ From Flutter to Backend: How to Build Production-Grade REST APIs with Dart and Serverpod ]]>
                </title>
                <description>
                    <![CDATA[ Serverpod is one of the most performant backend frameworks built on Dart. It's a fully opinionated backend framework that comes with its own ORM, its own code generation system, migration tooling, aut ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-production-grade-rest-apis-with-dart-and-serverpod/</link>
                <guid isPermaLink="false">6a1f040ecf96043972a543a7</guid>
                
                    <category>
                        <![CDATA[ Serverpod ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Server side rendering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dart ]]>
                    </category>
                
                    <category>
                        <![CDATA[ backend ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwaseyi Fatunmole ]]>
                </dc:creator>
                <pubDate>Tue, 02 Jun 2026 16:25:50 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/910a29c7-b380-4432-bc3c-d2c6930c3ac9.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Serverpod is one of the most performant backend frameworks built on Dart. It's a fully opinionated backend framework that comes with its own ORM, its own code generation system, migration tooling, authentication module, and deployment platform.</p>
<p>If you use a tool like Shelf to build your API, you assemble everything yourself. You choose your packages, write your own middleware, manage your own database connection, and wire every piece together manually. That's the Shelf way, and it teaches you exactly how server-side Dart works under the hood.</p>
<p>Serverpod is a different philosophy entirely.</p>
<p>Where Shelf gives you primitives, Serverpod gives you a complete system. You define your models in YAML, run a generator, and get fully typed database classes, serialization, and client-side code produced automatically.</p>
<p>For Flutter engineers, this feels immediately familiar. It's the same kind of productivity you get from the Flutter toolchain itself, applied to the backend.</p>
<p>In this article, we're going to build a User and Profile Management REST API from scratch using Serverpod. You'll learn how Serverpod's code generation, built-in ORM, and endpoint system work, and you'll have a fully deployed backend by the end.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-how-serverpod-differs-from-shelf">How Serverpod Differs from Shelf</a></p>
</li>
<li><p><a href="#heading-installing-serverpod">Installing Serverpod</a></p>
</li>
<li><p><a href="#heading-creating-the-project">Creating the Project</a></p>
</li>
<li><p><a href="#heading-understanding-the-project-structure">Understanding the Project Structure</a></p>
</li>
<li><p><a href="#heading-serverpod-core-concepts">Serverpod Core Concepts</a></p>
<ul>
<li><p><a href="#heading-endpoints-and-the-session-object">Endpoints and the Session Object</a></p>
</li>
<li><p><a href="#heading-model-files-and-code-generation">Model Files and Code Generation</a></p>
</li>
<li><p><a href="#heading-the-built-in-orm">The Built-in ORM</a></p>
</li>
<li><p><a href="#heading-migrations">Migrations</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-starting-the-development-server">Starting the Development Server</a></p>
</li>
<li><p><a href="#heading-defining-the-models">Defining the Models</a></p>
<ul>
<li><p><a href="#heading-the-user-model">The User Model</a></p>
</li>
<li><p><a href="#heading-the-profile-model">The Profile Model</a></p>
</li>
<li><p><a href="#heading-running-code-generation">Running Code Generation</a></p>
</li>
<li><p><a href="#heading-creating-and-applying-migrations">Creating and Applying Migrations</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-building-the-api">Building the API</a></p>
<ul>
<li><p><a href="#heading-the-auth-endpoint">The Auth Endpoint</a></p>
</li>
<li><p><a href="#heading-the-user-endpoint">The User Endpoint</a></p>
</li>
<li><p><a href="#heading-the-profile-endpoint">The Profile Endpoint</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-authentication">Authentication</a></p>
<ul>
<li><p><a href="#heading-password-hashing-and-jwt">Password Hashing and JWT</a></p>
</li>
<li><p><a href="#heading-protecting-endpoints">Protecting Endpoints</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-error-handling-in-serverpod">Error Handling in Serverpod</a></p>
</li>
<li><p><a href="#heading-testing-the-api">Testing the API</a></p>
</li>
<li><p><a href="#heading-deployment">Deployment</a></p>
<ul>
<li><p><a href="#heading-deploying-with-docker-and-flyio">Deploying with Docker and Fly.io</a></p>
</li>
<li><p><a href="#heading-deploying-with-serverpod-cloud">Deploying with Serverpod Cloud</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before starting, you should have:</p>
<ul>
<li><p>Familiarity with Dart and Flutter development</p>
</li>
<li><p>Understanding of REST API concepts, endpoints, HTTP methods, status codes</p>
</li>
<li><p>Docker Desktop installed and running</p>
</li>
<li><p>Flutter SDK installed (Serverpod requires it even for server-only projects)</p>
</li>
<li><p>A Fly.io account or a Serverpod Cloud account for deployment</p>
</li>
</ul>
<h2 id="heading-how-serverpod-differs-from-shelf">How Serverpod Differs from Shelf</h2>
<p>Before writing a single line of code, it's worth understanding the fundamental difference in philosophy between Shelf and Serverpod. This will make every design decision in the framework feel deliberate rather than arbitrary.</p>
<p>With Shelf, you write everything. Request parsing, response formatting, database queries, migrations, auth, and logging. Every piece is explicit code that you understand because you wrote it.</p>
<p>With Serverpod, you define things and the framework writes code for you. You define a model in YAML, run serverpod generate, and get a full Dart class with database bindings, serialization, and client-side access automatically. You define an endpoint method, and the framework handles routing, parameter extraction, and response formatting.</p>
<p>This is the same trade-off Flutter makes compared to building with raw platform APIs. Flutter writes the layout engine, the rendering pipeline, and the gesture system for you. You focus on your product logic. Serverpod makes the same bet on the backend.</p>
<p>The cost of that productivity is flexibility. Serverpod has strong opinions about how things should be structured. If your use case fits those opinions, development is extremely fast. If it doesn't, you're working against the framework.</p>
<p>For the User and Profile Management API we're building here, Serverpod is a very good fit.</p>
<h2 id="heading-installing-serverpod">Installing Serverpod</h2>
<p>Serverpod requires Flutter to be installed, even for server-only work. This is because its toolchain builds client packages alongside the server package during project creation.</p>
<p>Install the Serverpod CLI globally:</p>
<pre><code class="language-bash">dart pub global activate serverpod_cli
</code></pre>
<p>Verify the installation:</p>
<pre><code class="language-bash">serverpod
# Should print the Serverpod CLI help
</code></pre>
<p>Make sure Docker Desktop is running before proceeding. Serverpod uses Docker to manage PostgreSQL and Redis for local development.</p>
<h2 id="heading-creating-the-project">Creating the Project</h2>
<pre><code class="language-bash">serverpod create user_profile_api
cd user_profile_api
</code></pre>
<p>This single command creates three Dart packages:</p>
<pre><code class="language-plaintext">user_profile_api/
  user_profile_api_server/    ← your server code
  user_profile_api_client/    ← auto-generated client (do not edit)
  user_profile_api_flutter/   ← Flutter app pre-configured to connect
</code></pre>
<p>For this article, everything we write lives in user_profile_api_server. The client and Flutter packages are generated automatically and used when you want a Flutter frontend talking to your Serverpod backend.</p>
<h2 id="heading-understanding-the-project-structure">Understanding the Project Structure</h2>
<p>Inside user_profile_api_server:</p>
<pre><code class="language-plaintext">user_profile_api_server/
  bin/
    main.dart                  ← entry point
  lib/
    src/
      endpoints/               ← your endpoint classes live here
      generated/               ← auto-generated code (never edit manually)
    user_profile_api_server.dart
  config/
    development.yaml           ← database and server config
    staging.yaml
    production.yaml
    passwords.yaml             ← database passwords
  migrations/                  ← auto-generated migration files
  web/                         ← optional web server files
  Dockerfile
  docker-compose.yaml
  pubspec.yaml
</code></pre>
<p>The most important thing to understand about this structure is the generated/ folder. Everything in there is produced by serverpod generate and should never be edited manually. When you change a model or endpoint, you run the generator and it rewrites that folder entirely.</p>
<p>The config/ folder holds environment-specific configuration. The development.yaml file is preconfigured to work with the Docker containers Serverpod spins up locally.</p>
<h2 id="heading-serverpod-core-concepts">Serverpod Core Concepts</h2>
<h3 id="heading-endpoints-and-the-session-object">Endpoints and the Session Object</h3>
<p>In Serverpod, an endpoint is a class that extends Endpoint. Every public method on that class becomes an API call that clients can make. There's no routing configuration, no handler registration, and no middleware mounting. The framework discovers and registers your endpoints automatically during code generation.</p>
<pre><code class="language-dart">import 'package:serverpod/serverpod.dart';

class UserEndpoint extends Endpoint {
  Future&lt;String&gt; greet(Session session, String name) async {
    return 'Hello, $name!';
  }
}
</code></pre>
<p>The Session object is the most important parameter in Serverpod. It's passed to every endpoint method and gives you access to:</p>
<ul>
<li><p>session.db for database operations</p>
</li>
<li><p>session.auth for authentication information</p>
</li>
<li><p>session.log for structured logging</p>
</li>
<li><p>session.caches for caching</p>
</li>
<li><p>session.messages for real-time messaging</p>
</li>
</ul>
<p>Think of Session as Serverpod's equivalent of Flutter's BuildContext. It's the gateway to everything the framework provides, and it's always the first parameter.</p>
<h3 id="heading-model-files-and-code-generation">Model Files and Code Generation</h3>
<p>This is where Serverpod's approach diverges most sharply from Shelf. Instead of writing Dart model classes manually, you define your data structures in .spy.yaml files and let Serverpod generate the Dart classes.</p>
<p>A model file for a Company looks like this:</p>
<pre><code class="language-yaml">class: Company
table: company
fields:
  name: String
  foundedDate: DateTime?
</code></pre>
<p>Running serverpod generate produces a full Dart class with:</p>
<ul>
<li><p>Immutable fields with correct types</p>
</li>
<li><p>toJson and fromJson for serialization</p>
</li>
<li><p>Database bindings through the db static accessor</p>
</li>
<li><p>Constructor and copyWith method</p>
</li>
<li><p>The same class is generated in the client package so the Flutter app can use it directly</p>
</li>
</ul>
<p>This is the core productivity gain. You define the shape once in YAML and get a consistent, typed model that works across the server, the database, and the client without duplication.</p>
<h3 id="heading-the-built-in-orm">The Built-in ORM</h3>
<p>Serverpod's ORM uses the generated model classes directly. All database operations go through the static db accessor on your model:</p>
<pre><code class="language-dart">// Insert a row
var company = Company(name: 'Serverpod Corp', foundedDate: DateTime.now());
company = await Company.db.insertRow(session, company);

// Find by ID
var found = await Company.db.findById(session, company.id!);

// Find with condition
var result = await Company.db.findFirstRow(
  session,
  where: (t) =&gt; t.name.equals('Serverpod Corp'),
);

// Find all with ordering
var all = await Company.db.find(
  session,
  orderBy: (t) =&gt; t.name,
);

// Update
company = company.copyWith(name: 'New Name');
await Company.db.updateRow(session, company);

// Delete
await Company.db.deleteRow(session, company);
</code></pre>
<p>The where parameter uses a type-safe expression builder. The t parameter gives you typed access to the table's columns, so you get autocompletion and compile-time checks on your query conditions. No raw SQL, no string-based column names, no runtime surprises.</p>
<h3 id="heading-migrations">Migrations</h3>
<p>When you change a model, Serverpod generates a migration automatically:</p>
<pre><code class="language-bash">serverpod create-migration
</code></pre>
<p>This creates a SQL migration file in the migrations/ directory. Apply it when starting the server:</p>
<pre><code class="language-bash">dart bin/main.dart --apply-migrations
</code></pre>
<p>Serverpod tracks which migrations have been applied and runs only the new ones. The migration system is fully integrated with the model system, so there's no drift between your Dart classes and your database schema.</p>
<h2 id="heading-starting-the-development-server">Starting the Development Server</h2>
<p>Before writing any code, get the development environment running.</p>
<p>Start the Docker containers (PostgreSQL and Redis):</p>
<pre><code class="language-bash">cd user_profile_api_server
docker compose up --build --detach
</code></pre>
<p>Start the server with migrations applied:</p>
<pre><code class="language-bash">dart bin/main.dart --apply-migrations
</code></pre>
<p>You should see:</p>
<pre><code class="language-plaintext">SERVERPOD version: 2.x.x, mode: development
Insights listening on port 8081
Server default listening on port 8080
Webserver listening on port 8082
</code></pre>
<p>Three ports: Port 8080 is the main API server. Port 8081 is the Serverpod Insights tool for monitoring. Port 8082 is an optional web server. For this article, we'll work exclusively with port 8080.</p>
<h2 id="heading-defining-the-models">Defining the Models</h2>
<h3 id="heading-the-user-model">The User Model</h3>
<p>Create lib/src/models/user.spy.yaml in the server package:</p>
<pre><code class="language-yaml">class: AppUser
table: app_users
fields:
  email: String
  passwordHash: String
  firstName: String
  lastName: String
  isActive: bool, default=true
indexes:
  app_users_email_idx:
    fields: email
    unique: true
</code></pre>
<p>A few things to note here. The class is named AppUser rather than User to avoid conflicts with Serverpod's internal User class from the auth module. The table key defines the PostgreSQL table name. The indexes block creates a unique index on the email column, enforcing uniqueness at the database level.</p>
<p>Serverpod automatically adds an id field of type int? to every model with a table key. You don't declare it yourself.</p>
<h3 id="heading-the-profile-model">The Profile Model</h3>
<p>Create lib/src/models/profile.spy.yaml:</p>
<pre><code class="language-yaml">class: Profile
table: profiles
fields:
  userId: int
  bio: String?
  avatarUrl: String?
  phone: String?
  location: String?
  website: String?
indexes:
  profiles_user_id_idx:
    fields: userId
    unique: true
</code></pre>
<p>userId is an int referencing the id of an AppUser. Serverpod's model system doesn't yet have a foreign key declaration syntax in the YAML, so referential integrity is handled at the application layer in the endpoint logic.</p>
<h3 id="heading-running-code-generation">Running Code Generation</h3>
<p>With both model files in place, run the generator:</p>
<pre><code class="language-bash">serverpod generate
</code></pre>
<p>This produces Dart classes in lib/src/generated/. For AppUser, you get:</p>
<pre><code class="language-dart">// This is auto-generated, never edit directly
class AppUser extends SerializableEntity {
  AppUser({
    this.id,
    required this.email,
    required this.passwordHash,
    required this.firstName,
    required this.lastName,
    this.isActive = true,
  });

  int? id;
  String email;
  String passwordHash;
  String firstName;
  String lastName;
  bool isActive;

  // db accessor for ORM operations
  static final db = AppUserRepository._();

  // Serialization methods
  factory AppUser.fromJson(Map&lt;String, dynamic&gt; jsonSerialization, ...) { ... }
  Map&lt;String, dynamic&gt; toJson() { ... }
}
</code></pre>
<p>The generated code is what your endpoints interact with. You never write this by hand.</p>
<h3 id="heading-creating-and-applying-migrations">Creating and Applying Migrations</h3>
<p>With the models generated, create the migration:</p>
<pre><code class="language-bash">serverpod create-migration
</code></pre>
<p>This creates timestamped SQL files in migrations/. Apply them:</p>
<pre><code class="language-bash">dart bin/main.dart --apply-migrations
</code></pre>
<p>Your app_users and profiles tables now exist in PostgreSQL with the correct columns and indexes.</p>
<h2 id="heading-building-the-api">Building the API</h2>
<h3 id="heading-the-auth-endpoint">The Auth Endpoint</h3>
<p>Create lib/src/endpoints/auth_endpoint.dart:</p>
<pre><code class="language-dart">import 'package:serverpod/serverpod.dart';
import 'package:bcrypt/bcrypt.dart';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import '../generated/protocol.dart';

class AuthEndpoint extends Endpoint {
  Future&lt;Map&lt;String, dynamic&gt;&gt; register(
    Session session,
    String email,
    String password,
    String firstName,
    String lastName,
  ) async {
    if (email.isEmpty || password.isEmpty || firstName.isEmpty || lastName.isEmpty) {
      throw Exception('All fields are required');
    }

    if (password.length &lt; 8) {
      throw Exception('Password must be at least 8 characters');
    }

    // Check for existing user
    final existing = await AppUser.db.findFirstRow(
      session,
      where: (t) =&gt; t.email.equals(email),
    );

    if (existing != null) {
      throw Exception('An account with this email already exists');
    }

    final passwordHash = BCrypt.hashpw(password, BCrypt.gensalt());

    var user = AppUser(
      email: email,
      passwordHash: passwordHash,
      firstName: firstName,
      lastName: lastName,
    );

    user = await AppUser.db.insertRow(session, user);

    final token = _generateToken(user);

    return {
      'user': _sanitizeUser(user),
      'token': token,
    };
  }

  Future&lt;Map&lt;String, dynamic&gt;&gt; login(
    Session session,
    String email,
    String password,
  ) async {
    if (email.isEmpty || password.isEmpty) {
      throw Exception('Email and password are required');
    }

    final user = await AppUser.db.findFirstRow(
      session,
      where: (t) =&gt; t.email.equals(email),
    );

    if (user == null || !BCrypt.checkpw(password, user.passwordHash)) {
      throw Exception('Invalid email or password');
    }

    if (!user.isActive) {
      throw Exception('This account has been deactivated');
    }

    final token = _generateToken(user);

    return {
      'user': _sanitizeUser(user),
      'token': token,
    };
  }

  String _generateToken(AppUser user) {
    final jwt = JWT({'sub': user.id, 'email': user.email});
    return jwt.sign(SecretKey(_jwtSecret), expiresIn: const Duration(hours: 24));
  }

  // Never return the password hash to the client
  Map&lt;String, dynamic&gt; _sanitizeUser(AppUser user) =&gt; {
        'id': user.id,
        'email': user.email,
        'firstName': user.firstName,
        'lastName': user.lastName,
        'isActive': user.isActive,
      };

  // Read from Serverpod's config system
  String get _jwtSecret =&gt;
      Session.serverpod.getPassword('jwtSecret') ?? 'fallback_dev_secret';
}
</code></pre>
<p>Serverpod endpoints return typed values. When you return a Map&lt;String, dynamic&gt;, Serverpod serializes it automatically. When you throw an Exception, Serverpod catches it and returns a structured error response to the client. No manual response formatting, no status code management for common cases.</p>
<h3 id="heading-the-user-endpoint">The User Endpoint</h3>
<p>Create lib/src/endpoints/user_endpoint.dart:</p>
<pre><code class="language-dart">import 'package:serverpod/serverpod.dart';
import '../generated/protocol.dart';

class UserEndpoint extends Endpoint {
  @override
  bool get requireLogin =&gt; true;

  Future&lt;List&lt;Map&lt;String, dynamic&gt;&gt;&gt; getAll(Session session) async {
    final users = await AppUser.db.find(
      session,
      where: (t) =&gt; t.isActive.equals(true),
      orderBy: (t) =&gt; t.id,
    );

    return users.map(_sanitizeUser).toList();
  }

  Future&lt;Map&lt;String, dynamic&gt;&gt; getById(Session session, int userId) async {
    final user = await AppUser.db.findById(session, userId);

    if (user == null || !user.isActive) {
      throw Exception('User not found');
    }

    return _sanitizeUser(user);
  }

  Future&lt;Map&lt;String, dynamic&gt;&gt; update(
    Session session,
    int userId,
    String? firstName,
    String? lastName,
  ) async {
    final user = await AppUser.db.findById(session, userId);

    if (user == null || !user.isActive) {
      throw Exception('User not found');
    }

    final updated = user.copyWith(
      firstName: firstName ?? user.firstName,
      lastName: lastName ?? user.lastName,
    );

    await AppUser.db.updateRow(session, updated);
    return _sanitizeUser(updated);
  }

  Future&lt;void&gt; delete(Session session, int userId) async {
    final user = await AppUser.db.findById(session, userId);

    if (user == null || !user.isActive) {
      throw Exception('User not found');
    }

    // Soft delete
    final deactivated = user.copyWith(isActive: false);
    await AppUser.db.updateRow(session, deactivated);
  }

  Map&lt;String, dynamic&gt; _sanitizeUser(AppUser user) =&gt; {
        'id': user.id,
        'email': user.email,
        'firstName': user.firstName,
        'lastName': user.lastName,
        'isActive': user.isActive,
      };
}
</code></pre>
<p>Notice @override bool get requireLogin =&gt; true. This is Serverpod's built-in mechanism for protecting endpoints. When this getter returns true, Serverpod validates the authentication token on every request to this endpoint before the method is called. Unauthenticated requests are rejected automatically by the framework.</p>
<h3 id="heading-the-profile-endpoint">The Profile Endpoint</h3>
<p>Create lib/src/endpoints/profile_endpoint.dart:</p>
<pre><code class="language-dart">import 'package:serverpod/serverpod.dart';
import '../generated/protocol.dart';

class ProfileEndpoint extends Endpoint {
  @override
  bool get requireLogin =&gt; true;

  Future&lt;Map&lt;String, dynamic&gt;&gt; getByUserId(
    Session session,
    int userId,
  ) async {
    final user = await AppUser.db.findById(session, userId);
    if (user == null || !user.isActive) {
      throw Exception('User not found');
    }

    final profile = await Profile.db.findFirstRow(
      session,
      where: (t) =&gt; t.userId.equals(userId),
    );

    if (profile == null) {
      throw Exception('Profile not found');
    }

    return _profileToMap(profile);
  }

  Future&lt;Map&lt;String, dynamic&gt;&gt; create(
    Session session,
    int userId,
    String? bio,
    String? avatarUrl,
    String? phone,
    String? location,
    String? website,
  ) async {
    final user = await AppUser.db.findById(session, userId);
    if (user == null || !user.isActive) {
      throw Exception('User not found');
    }

    final existing = await Profile.db.findFirstRow(
      session,
      where: (t) =&gt; t.userId.equals(userId),
    );

    if (existing != null) {
      throw Exception('Profile already exists for this user');
    }

    var profile = Profile(
      userId: userId,
      bio: bio,
      avatarUrl: avatarUrl,
      phone: phone,
      location: location,
      website: website,
    );

    profile = await Profile.db.insertRow(session, profile);
    return _profileToMap(profile);
  }

  Future&lt;Map&lt;String, dynamic&gt;&gt; update(
    Session session,
    int userId,
    String? bio,
    String? avatarUrl,
    String? phone,
    String? location,
    String? website,
  ) async {
    final profile = await Profile.db.findFirstRow(
      session,
      where: (t) =&gt; t.userId.equals(userId),
    );

    if (profile == null) {
      throw Exception('Profile not found');
    }

    final updated = profile.copyWith(
      bio: bio ?? profile.bio,
      avatarUrl: avatarUrl ?? profile.avatarUrl,
      phone: phone ?? profile.phone,
      location: location ?? profile.location,
      website: website ?? profile.website,
    );

    await Profile.db.updateRow(session, updated);
    return _profileToMap(updated);
  }

  Map&lt;String, dynamic&gt; _profileToMap(Profile profile) =&gt; {
        'id': profile.id,
        'userId': profile.userId,
        'bio': profile.bio,
        'avatarUrl': profile.avatarUrl,
        'phone': profile.phone,
        'location': profile.location,
        'website': profile.website,
      };
}
</code></pre>
<p>After adding these endpoints, run the generator again so Serverpod registers them:</p>
<pre><code class="language-bash">serverpod generate
</code></pre>
<h2 id="heading-authentication">Authentication</h2>
<h3 id="heading-password-hashing-and-jwt">Password Hashing and JWT</h3>
<p>Add the required packages to pubspec.yaml in the server package:</p>
<pre><code class="language-yaml">dependencies:
  serverpod: ^2.5.0
  bcrypt: ^1.1.3
  dart_jsonwebtoken: ^2.12.0
</code></pre>
<p>Then run dart pub get.</p>
<p>The _generateToken and _sanitizeUser helpers in the auth endpoint handle password hashing and JWT generation. The JWT secret is read from Serverpod's built-in password management system via Session.serverpod.getPassword('jwtSecret').</p>
<p>Add the secret to config/passwords.yaml:</p>
<pre><code class="language-yaml">development:
  database: 'dart_password'
  jwtSecret: 'your_development_jwt_secret_here'
</code></pre>
<p>This file is already in .gitignore by default in a Serverpod project. Production secrets are injected via environment variables or Serverpod Cloud's secret management.</p>
<h3 id="heading-protecting-endpoints">Protecting Endpoints</h3>
<p>Serverpod has two levels of endpoint protection:</p>
<p>requireLogin — rejects unauthenticated requests automatically:</p>
<pre><code class="language-dart">@override
bool get requireLogin =&gt; true;
</code></pre>
<p>requiredScopes — requires specific permission scopes:</p>
<pre><code class="language-dart">@override
Set&lt;Scope&gt; get requiredScopes =&gt; {Scope.admin};
</code></pre>
<p>For the User and Profile endpoints in this article, requireLogin is sufficient. The token from the login response is passed in the Authorization header on every subsequent request, and Serverpod validates it before the endpoint method is called.</p>
<p>Verifying the token inside an endpoint to get the current user's ID:</p>
<pre><code class="language-dart">Future&lt;void&gt; someProtectedMethod(Session session) async {
  final authInfo = await session.authenticated;

  if (authInfo == null) {
    throw Exception('Not authenticated');
  }

  final userId = authInfo.userId;
  // proceed with userId
}
</code></pre>
<h2 id="heading-error-handling-in-serverpod">Error Handling in Serverpod</h2>
<p>Serverpod handles exceptions thrown from endpoint methods and converts them into structured error responses automatically. When you throw:</p>
<pre><code class="language-dart">throw Exception('User not found');
</code></pre>
<p>The client receives a structured error response. For more granular control, Serverpod provides typed exceptions:</p>
<pre><code class="language-dart">throw ServerpodClientException('User not found', statusCode: 404);
</code></pre>
<p>For server-side logging without exposing details to the client:</p>
<pre><code class="language-dart">session.log('Unexpected error during user creation', level: LogLevel.error);
throw Exception('An internal error occurred');
</code></pre>
<p>Serverpod's logging system stores logs in the database and makes them queryable through the Insights dashboard on port 8081. Every request is automatically logged with timing information, endpoint name, and outcome, no additional middleware required.</p>
<h2 id="heading-testing-the-api">Testing the API</h2>
<p>Serverpod exposes its endpoints over HTTP. You can test them directly with curl, though the request format follows Serverpod's RPC convention rather than a traditional REST structure.</p>
<p>The generated URL pattern for an endpoint method is:</p>
<pre><code class="language-plaintext">POST /[endpoint]/[method]
</code></pre>
<p>With a JSON body containing the method parameters.</p>
<p>Register a user:</p>
<pre><code class="language-bash">curl http://localhost:8080/auth/register \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "email": "seyi@example.com",
    "password": "securepassword",
    "firstName": "Seyi",
    "lastName": "Dev"
  }'
</code></pre>
<p>Login:</p>
<pre><code class="language-bash">curl http://localhost:8080/auth/login \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"email": "seyi@example.com", "password": "securepassword"}'
</code></pre>
<p>Get all users (authenticated):</p>
<pre><code class="language-bash">curl http://localhost:8080/user/getAll \
  -X POST \
  -H "Authorization: Bearer eyJhbGci..."
</code></pre>
<p>Get user by ID:</p>
<pre><code class="language-bash">curl http://localhost:8080/user/getById \
  -X POST \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{"userId": 1}'
</code></pre>
<p>Create a profile:</p>
<pre><code class="language-bash">curl http://localhost:8080/profile/create \
  -X POST \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{
    "userId": 1,
    "bio": "Flutter engineer turned backend developer",
    "location": "Lagos, Nigeria",
    "website": "https://example.com"
  }'
</code></pre>
<p>Update a user:</p>
<pre><code class="language-bash">curl http://localhost:8080/user/update \
  -X POST \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{"userId": 1, "firstName": "Oluwaseyi"}'
</code></pre>
<p>Delete a user:</p>
<pre><code class="language-bash">curl http://localhost:8080/user/delete \
  -X POST \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{"userId": 1}'
</code></pre>
<h2 id="heading-deployment">Deployment</h2>
<h3 id="heading-deploying-with-docker-and-flyio">Deploying with Docker and Fly.io</h3>
<p>Serverpod generates a Dockerfile as part of the project creation. It's located in user_profile_api_server/Dockerfile and is ready to use.</p>
<p>The included docker-compose.yaml in the server package manages PostgreSQL and Redis for local development. For production deployment on Fly.io, the process follows the same Docker-based pattern covered in the deployment section below.</p>
<p><strong>Step 1 — Authenticate with Fly:</strong></p>
<pre><code class="language-bash">fly auth login
</code></pre>
<p><strong>Step 2 — Launch the app from the server directory:</strong></p>
<pre><code class="language-bash">cd user_profile_api_server
fly launch
</code></pre>
<p><strong>Step 3 — Set production secrets:</strong></p>
<pre><code class="language-bash">fly secrets set JWT_SECRET="your_production_jwt_secret"
</code></pre>
<p><strong>Step 4 — Update the production config:</strong></p>
<p>Edit config/production.yaml with your Fly-provisioned database connection details. Fly injects the DATABASE_URL environment variable which you map to the Serverpod config format.</p>
<p><strong>Step 5 — Deploy:</strong></p>
<pre><code class="language-bash">fly deploy
</code></pre>
<p><strong>Step 6 — Apply migrations on first deploy:</strong></p>
<pre><code class="language-bash">fly ssh console
dart bin/main.dart --apply-migrations --mode production
</code></pre>
<h3 id="heading-deploying-with-serverpod-cloud">Deploying with Serverpod Cloud</h3>
<p>Serverpod Cloud is the native deployment platform built specifically for Serverpod applications. It handles database provisioning, scaling, monitoring, and deployments with minimal configuration.</p>
<p>Install the Serverpod Cloud CLI:</p>
<pre><code class="language-bash">dart pub global activate serverpod_cloud_cli
</code></pre>
<p>Authenticate:</p>
<pre><code class="language-bash">scloud login
</code></pre>
<p>Create a project in the Serverpod Cloud dashboard at cloud.serverpod.dev, then link your local project:</p>
<pre><code class="language-bash">scloud link --project-id your-project-id
</code></pre>
<p>Deploy:</p>
<pre><code class="language-bash">scloud deploy
</code></pre>
<p>Serverpod Cloud provisions a managed PostgreSQL database, applies your migrations, and deploys your server automatically. It also provides the Insights dashboard for monitoring requests, logs, and performance in production.</p>
<p>For teams already committed to the Serverpod ecosystem, Serverpod Cloud is the fastest path to a production deployment.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Serverpod takes a fundamentally different approach from Shelf. Where Shelf gives you control, Serverpod gives you speed. You define your models in YAML, run a generator, and get database classes, serialization, and client code produced automatically. You write an endpoint method and the framework handles routing, parameter extraction, authentication, and error formatting.</p>
<p>The ORM is the strongest part of the experience. Type-safe query expressions, automatic migration generation, and no SQL drift between your code and your schema make database work noticeably faster and safer than raw SQL.</p>
<p>The cost is rigidity. Serverpod's URL structure, serialization format, and architectural conventions aren't optional. If your API needs to conform to a specific REST structure that differs from Serverpod's RPC style, you'll be working against the framework.</p>
<p>For greenfield Flutter backends where the Dart client will consume the API, Serverpod is hard to beat. The code sharing between server and client, the automatic client generation, and the tight toolchain integration make it the most productive Dart backend option available.</p>
<p>For APIs that need to serve multiple clients, conform to external REST conventions, or integrate with existing infrastructure that doesn't expect Serverpod's format, a lower-level tool like Shelf gives you more control. If you want to see how the same User and Profile Management API is built with Shelf and compare the two approaches directly, you can <a href="https://www.freecodecamp.org/news/how-to-build-and-ship-production-rest-apis-with-dart-and-shelf">find that article here</a>.</p>
<p>Knowing which tool fits which job is what separates a developer who knows a framework from one who understands backend development.</p>
<p>Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Server-Side Rendering in Next.js Apps for Better SEO ]]>
                </title>
                <description>
                    <![CDATA[ Server-side rendering (SSR) is a web development technique that can help improve your site's SEO. It does this by generating HTML content on the server in response to a user's request.  This approach contrasts with client-side rendering (CSR), where ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/server-side-rendering-in-next-js-for-improved-seo/</link>
                <guid isPermaLink="false">66c4c415bd556981b1bdc441</guid>
                
                    <category>
                        <![CDATA[ HTML ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SEO ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Server side rendering ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Joan Ayebola ]]>
                </dc:creator>
                <pubDate>Wed, 17 Jul 2024 18:02:11 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/07/How-to-Use-Server-Side-Rendering-in-Next.js-Apps-for-Better-SEO-Cover-Image.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Server-side rendering (SSR) is a web development technique that can help improve your site's SEO. It does this by generating HTML content on the server in response to a user's request. </p>
<p>This approach contrasts with client-side rendering (CSR), where content is delivered as a basic HTML shell, and JavaScript fetches and displays data in the browser.</p>
<p>SSR offers significant SEO advantages, making it a perfect fit for Next.js, a popular React framework. Let's discuss how using SSR with Next.js can elevate your website's search engine visibility.</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ol>
<li><a class="post-section-overview" href="#heading-what-is-server-side-rendering">What is Server-Side Rendering</a>?</li>
<li><a class="post-section-overview" href="#heading-how-to-get-started-with-nextjs-and-ssr">How to Get Started with Next.js and SSR</a></li>
<li><a class="post-section-overview" href="#heading-how-nextjs-enables-server-side-rendering">How Next.js Enables Server-side Rendering</a></li>
<li><a class="post-section-overview" href="#heading-data-fetching-with-getstaticprops-and-getserversideprops">Data Fetching with getStaticProps and getServerSideProps</a></li>
<li><a class="post-section-overview" href="#heading-benefits-of-ssr-for-seo-with-nextjs-and-how-to-optimize">Benefits of SSR for SEO with Next.js and How to Optimize</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ol>
<h2 id="heading-what-is-server-side-rendering">What is Server-Side Rendering?</h2>
<p>Server-side rendering (SSR) is a technique in web development where the web server generates the complete HTML content of a web page before sending it to the user's browser. </p>
<p>This is unlike client-side rendering (CSR), where the browser downloads a basic HTML structure and then uses JavaScript to fetch and display the content.</p>
<h2 id="heading-how-to-get-started-with-nextjs-and-ssr">How to Get Started with Next.js and SSR</h2>
<p>Getting started with Next.js and server-side rendering (SSR) involves a few steps. Here's a step-by-step guide to help you set up a Next.js project and implement SSR.</p>
<h3 id="heading-step-1-install-nextjs">Step 1: Install Next.js</h3>
<p>First, you need to install Next.js. You can do this using <code>create-next-app</code>, which sets up a new Next.js project with a default configuration. Run the following command in your terminal:</p>
<pre><code class="lang-bash">npx create-next-app my-next-app
<span class="hljs-built_in">cd</span> my-next-app
npm run dev
</code></pre>
<p>This command creates a new Next.js application in a folder called <code>my-next-app</code> and starts the development server.</p>
<h3 id="heading-step-2-understand-the-project-structure">Step 2: Understand the Project Structure</h3>
<p>Next.js organizes the project with some default folders and files:</p>
<ul>
<li><strong><code>pages/</code></strong>: This folder contains all the pages of your application. Each file represents a route in your app.</li>
<li><strong><code>public/</code></strong>: Static assets like images can be placed here.</li>
<li><strong><code>styles/</code></strong>: Contains CSS files for styling your application.</li>
</ul>
<h3 id="heading-step-3-create-a-simple-page-with-ssr">Step 3: Create a Simple Page with SSR</h3>
<p>Now, let's create a simple page that uses SSR.</p>
<p>Create a new file <code>pages/index.js</code>:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// pages/index.js</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> Home = <span class="hljs-function">(<span class="hljs-params">{ data }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Welcome to Next.js with SSR<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Data fetched from the server: {data.message}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getServerSideProps</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Fetch data from an API or other sources</span>
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://api.example.com/data'</span>);
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> res.json();

  <span class="hljs-comment">// Return the data as props to the Home component</span>
  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      data,
    },
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Home;
</code></pre>
<p>Let's discuss this code in some detail. For the <code>home</code> component:</p>
<ul>
<li>The <code>Home</code> component is a functional component that accepts <code>props</code>.</li>
<li>The <code>data</code> prop contains the data fetched from the server.</li>
<li>Inside the component, we render a welcome message and the fetched data.</li>
</ul>
<p>The <code>getServerSideProps</code> function:</p>
<ul>
<li>This function is exported from the <code>pages/index.js</code> file.</li>
<li>It executes on the server for each request to this page.</li>
<li>Inside this function, you can perform asynchronous operations such as fetching data from an external API.</li>
<li>The fetched data is returned as an object with a <code>props</code> key. This object will be passed to the <code>Home</code> component as props.</li>
</ul>
<p>You can add error handling to the <code>getServerSideProps</code> function to manage any issues that might arise during data fetching. Here's an example:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getServerSideProps</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://api.example.com/data'</span>);
    <span class="hljs-keyword">if</span> (!res.ok) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Failed to fetch data'</span>);
    }
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> res.json();
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">props</span>: {
        data,
      },
    };
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(error);
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">props</span>: {
        <span class="hljs-attr">data</span>: { <span class="hljs-attr">message</span>: <span class="hljs-string">'Error fetching data'</span> },
      },
    };
  }
}
</code></pre>
<h4 id="heading-step-4-run-the-application">Step 4: Run the Application</h4>
<p>Start your development server if it's not already running:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>Open your browser and go to <code>http://localhost:3000</code>. You should see the message fetched from the API displayed on the page.</p>
<h2 id="heading-how-nextjs-enables-server-side-rendering">How Next.js Enables Server-Side Rendering</h2>
<p>Next.js provides a seamless way to enable SSR and Static Site Generation (SSG). It pre-renders every page by default. Depending on the use case, you can choose between SSR and SSG:</p>
<ul>
<li><strong>Server-Side Rendering (SSR)</strong>: Pages are rendered on each request.</li>
<li><strong>Static Site Generation (SSG)</strong>: Pages are generated at build time.</li>
</ul>
<p>Next.js determines which rendering method to use based on the functions you implement in your page components (<code>getStaticProps</code> and <code>getServerSideProps</code>).</p>
<h3 id="heading-nextjs-page-components">Next.js Page Components</h3>
<p>Next.js uses the <code>pages/</code> directory to define routes. Each file in this directory corresponds to a route in your application.</p>
<ul>
<li><code>pages/index.js</code> → <code>/</code></li>
<li><code>pages/about.js</code> → <code>/about</code></li>
<li><code>pages/posts/[id].js</code> → <code>/posts/:id</code></li>
</ul>
<p>Here's a basic example of a page component:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// pages/index.js</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> Home = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Welcome to Next.js<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is the home page.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Home;
</code></pre>
<h3 id="heading-data-fetching-with-getstaticprops-and-getserversideprops">Data Fetching with <code>getStaticProps</code> and <code>getServerSideProps</code></h3>
<p><code>getStaticProps</code> is used for static generation. It runs at build time and allows you to fetch data and pass it to your page as props. Use this for data that doesn't change often.</p>
<p>Example:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// pages/index.js</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> Home = <span class="hljs-function">(<span class="hljs-params">{ posts }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Blog Posts<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
        {posts.map(post =&gt; (
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{post.id}</span>&gt;</span>{post.title}<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        ))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-comment">// This function runs at build time</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Fetch data from an API</span>
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/posts'</span>);
  <span class="hljs-keyword">const</span> posts = <span class="hljs-keyword">await</span> res.json();

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      posts,
    },
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Home;
</code></pre>
<p><code>getServerSideProps</code> is used for server-side rendering. It runs on every request and allows you to fetch data at request time.</p>
<p>Example:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// pages/index.js</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> Home = <span class="hljs-function">(<span class="hljs-params">{ data }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Server-Side Rendering with Next.js<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Data fetched from the server: {data.message}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-comment">// This function runs on every request</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getServerSideProps</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Fetch data from an external API</span>
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://api.example.com/data'</span>);
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> res.json();

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      data,
    },
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Home;
</code></pre>
<h2 id="heading-benefits-of-ssr-for-seo-with-nextjs-and-how-to-optimize">Benefits of SSR for SEO with Next.js and How to Optimize</h2>
<p>In this section, we will look at the main benefits of using SSR for SEO and give easy-to-follow tips on how to make the most of these benefits with your Next.js application.</p>
<h3 id="heading-1-improved-indexing-by-search-engines">1. Improved Indexing by Search Engines</h3>
<p>Client-side rendering (CSR) can cause issues with search engines struggling to index content properly since it is rendered in the user's browser using JavaScript. </p>
<p>SSR, however, renders content on the server before sending it to the user's browser, ensuring the HTML is complete and can be easily crawled and indexed by search engines.</p>
<p><strong>Use SSR for important pages:</strong> Ensure that key pages, such as landing pages, blog posts, and product pages, are rendered on the server to facilitate better indexing.</p>
<p>Example – Using SSR for a blog post page:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// pages/blog/[id].js</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/router'</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">'next/head'</span>;

<span class="hljs-keyword">const</span> BlogPost = <span class="hljs-function">(<span class="hljs-params">{ post }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> router = useRouter();
  <span class="hljs-keyword">if</span> (router.isFallback) {
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Loading...<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
  }

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{post.title}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{post.excerpt}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{post.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{post.content}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getServerSideProps</span>(<span class="hljs-params">{ params }</span>) </span>{
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`https://api.example.com/posts/<span class="hljs-subst">${params.id}</span>`</span>);
  <span class="hljs-keyword">const</span> post = <span class="hljs-keyword">await</span> res.json();

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      post,
    },
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> BlogPost
</code></pre>
<ul>
<li><strong>BlogPost Component:</strong> This component displays a blog post. It uses <code>next/head</code> to manage meta tags, which are important for SEO.</li>
<li><strong>getServerSideProps Function:</strong> This function fetches data for the blog post from an API. It runs on the server for every request to this page, ensuring the content is ready for search engines to index when they crawl the page.</li>
</ul>
<h3 id="heading-2-faster-load-times">2. Faster Load Times</h3>
<p>Search engines like Google use page load speed as a ranking factor. SSR can improve initial load time because the server sends a fully rendered page to the browser, enhancing perceived performance and user experience.</p>
<p><strong>Optimize server response time:</strong> Ensure your server is optimized for quick responses. Use caching strategies to reduce server load.</p>
<p>Example – cache-control header for SSR:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getServerSideProps</span>(<span class="hljs-params">{ res }</span>) </span>{
  res.setHeader(<span class="hljs-string">'Cache-Control'</span>, <span class="hljs-string">'public, s-maxage=10, stale-while-revalidate=59'</span>);

  <span class="hljs-keyword">const</span> resData = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://api.example.com/data'</span>);
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> resData.json();

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      data,
    },
  };
}
</code></pre>
<ul>
<li><strong><code>getServerSideProps</code> Function:</strong> This function sets cache-control headers to cache the response for 10 seconds and serve stale content while revalidating for 59 seconds. This improves server response time and page load speed, contributing to better SEO.</li>
</ul>
<h3 id="heading-3-improved-social-media-sharing">3. Improved Social Media Sharing</h3>
<p>When sharing links on social media, platforms like Facebook and Twitter scrape the URL content to generate previews. SSR ensures that necessary metadata is available in the initial HTML, resulting in better previews and increased click-through rates.</p>
<p><strong>Manage meta tags with <code>next/head</code>:</strong> Use the <code>next/head</code> component to add meta tags for social media and SEO.</p>
<p>Example – Adding meta tags to a page:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">'next/head'</span>;

<span class="hljs-keyword">const</span> Page = <span class="hljs-function">(<span class="hljs-params">{ data }</span>) =&gt;</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{data.title}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{data.description}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:title"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{data.title}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{data.description}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:image"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{data.image}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:card"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"summary_large_image"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{data.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{data.content}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);
</code></pre>
<ul>
<li><strong><code>Page</code> Component:</strong> This component uses <code>next/head</code> to add SEO meta tags, including Open Graph tags for social media previews. This ensures that when the page is shared, social media platforms can generate rich previews with the provided metadata.</li>
</ul>
<h3 id="heading-4-enhanced-user-experience">4. Enhanced User Experience</h3>
<p>A faster, more responsive website enhances the overall user experience, leading to longer visit durations and lower bounce rates. Both factors positively influence your SEO rankings.</p>
<p><strong>Pre-render pages with static generation (SSG) for less dynamic content:</strong> Use SSG for pages that don’t change often to reduce server load and improve performance.</p>
<p>Example – Using SSG for a static page:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://api.example.com/static-data'</span>);
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> res.json();

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      data,
    },
    <span class="hljs-attr">revalidate</span>: <span class="hljs-number">10</span>, <span class="hljs-comment">// Revalidate at most once every 10 seconds</span>
  };
}

<span class="hljs-keyword">const</span> StaticPage = <span class="hljs-function">(<span class="hljs-params">{ data }</span>) =&gt;</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{data.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{data.content}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> StaticPage;
</code></pre>
<ul>
<li><strong><code>StaticPage</code> Component:</strong> This component displays static content fetched from an API.</li>
<li><strong><code>getStaticProps</code> Function:</strong> This function fetches data at build time and revalidates it every 10 seconds, ensuring the content is always fresh while reducing server load.</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Using server-side rendering and Next.js together is like giving your website an extra boost for search engines. With pre-built content for search engines and a smooth experience for visitors, your site is set up to be seen by more people naturally.  </p>
<p>This works great for any kind of website, from online stores to blogs. Next.js with SSR makes it easy to build a website that search engines love and users enjoy.</p>
<p>That's all for this article! If you'd like to continue the conversation or have questions, suggestions, or feedback, feel free to reach out to connect with me on <a target="_blank" href="https://ng.linkedin.com/in/joan-ayebola">LinkedIn</a>. And if you enjoyed this content, consider <a target="_blank" href="https://www.buymeacoffee.com/joanayebola">buying me a coffee</a> to support the creation of more developer-friendly contents.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
