How to Bootstrap a Remix Project

13 August 2024

Introduction to Remix

Remix is a React framework designed for building server-side rendering (SSR) full-stack applications. It integrates several powerful features such as server-side rendering, file-system-based routing, nested routes, enhanced error boundaries, and robust loading state handling. Remix allows developers to build fast, scalable, and SEO-friendly applications efficiently.

Difference between Remix and Next.js

Both Remix and Next.js are popular web development frameworks for React, but they have their own unique features and advantages.

Performance
  • Remix: Faster for dynamic content, optimized for slow networks using advanced data fetching strategies.

  • Next.js: Offers both static and dynamic content optimization through SSR and SSG.

Rendering Strategies
  • Next.js: Provides server-side rendering (SSR), static site generation (SSG), and client-side routing for diverse use cases.

  • Remix: Focuses primarily on SSR, offering server-rendered React applications for optimal performance.

Data Fetching
  • Next.js: Utilizes multiple methods such as getInitialProps, getServerSideProps, getStaticProps, and client-side fetching.

  • Remix: Simplifies the process with a single loader function that operates on the server.

Error Handling & Data Mutations
  • Next.js: Custom API routes and client-side management for error handling and data mutations.

  • Remix: Uses traditional HTML forms with enhanced server-side handling, simplifying error and data handling.

Use Cases
  • Next.js: Ideal for enterprise-grade applications requiring flexibility and scalability.

  • Remix: Best suited for projects prioritizing server-side rendering, SEO, and dynamic content performance.

Overall, while Next.js offers flexibility and a mature ecosystem, Remix provides a streamlined approach with enhanced performance for server-rendered applications.

Bootstrapping a Remix Project

Bootstrapping a Remix project can be a straightforward process if you follow the correct steps. Here’s how you can set up your project from scratch.

Prerequisites

Before you begin, ensure you have the following installed on your system:

  • Node.js and npm: Download and install them from the official Node.js website.

  • Code Editor: We recommend Visual Studio Code (VSCode) for editing your project files.

Step-by-Step Guide

Step 1: Creating a Remix Project

To create a new Remix project, open your terminal (or command prompt) and run the following command:

npx create-remix@latest

This command initializes a new Remix project. You will be prompted to answer some configuration questions such as selecting a package manager (npm or Yarn) and choosing between TypeScript and JavaScript for your project.

Step 2: Installing Dependencies

Once your project is created, navigate to your project directory and install the necessary dependencies by running:

For npm:

cd my-remix-project
npm install

For Yarn:

cd my-remix-project
yarn
Step 3: Running the Development Server

After installing the dependencies, you can start the development server by running:

For npm:

npm run dev

For Yarn:

yarn dev

This will start the Remix development server and open your project in the browser, allowing you to preview and work on your application.

Step 4: Project Structure Overview

A typical Remix project will have the following structure:

  • app directory contains the core application files.

  • routes directory holds all the route files.

  • entry.client.jsx and entry.server.jsx manage client and server entry points respectively.

  • root.jsx is the root component rendering the rest of the app using <Outlet />.

Practical Example: Configuring root.jsx

Here's an example of how you can configure root.jsx to include Bootstrap for styling and set up basic routing:

import { Links, LiveReload, Meta, Outlet } from "@remix-run/react";
import bootstrapCSS from "bootstrap/dist/css/bootstrap.min.css";

export const links = () => [{ rel: "stylesheet", href: bootstrapCSS }];

export const meta = () => ({
  charset: "utf-8",
  viewport: "width=device-width,initial-scale=1",
});

export default function App() {
  return (
    <Document>
      <Layout>
        <Outlet />
      </Layout>
    </Document>
  );
}

function Layout({ children }) {
  return (
    <>
      <nav className="navbar navbar-expand-lg navbar-light bg-light px-5 py-3">
        <a href="/" className="navbar-brand">Remix</a>
        <ul className="navbar-nav mr-auto">
          <li className="nav-item">
            <a className="nav-link" href="/posts">Posts</a>
          </li>
        </ul>
      </nav>
      <div className="container">{children}</div>
    </>
  );
}

function Document({ children }) {
  return (
    <html>
      <head>
        <Links />
        <Meta />
        <title>Remix Project</title>
      </head>
      <body>
        {children}
        {process.env.NODE_ENV === "development" ? <LiveReload /> : null}
      </body>
    </html>
  );
}

This setup includes Bootstrap for styling and sets up a basic navigation layout. The links and meta functions help in adding the necessary metadata and stylesheets.

Database Integration with Prisma for SQLite

To manage your application's data, integrating a database is essential. Prisma is a powerful ORM (Object-Relational Mapping) tool that can be used with Remix to work with databases effectively. In this guide, we will use Prisma with an SQLite database.

Step 1: Installing Prisma

First, install Prisma and its client package using npm:

npm install prisma @prisma/client

Step 2: Initializing Prisma

Next, initialize Prisma in your project with SQLite as the data source provider:

npx prisma init --datasource-provider sqlite

This command will create a prisma directory with a schema.prisma file where you can define your database schema.

Step 3: Defining the Schema

Open the schema.prisma file and define your data models. Here’s an example schema for a simple blog post model:

model Post {
  slug       String   @id
  title      String
  body       String
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt
}

Step 4: Pushing the Schema to the Database

After defining your schema, push it to the database using the following command:

npx prisma db push

Step 5: Seeding the Database

Create a prisma/seed.js file to seed your database with initial data. Here’s an example seed script:

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function seed() {
  await Promise.all(getPosts().map(post => {
    return prisma.post.create({ data: post });
  }));
}

seed();

function getPosts() {
  return [
    { title: 'JavaScript Performance Tips', slug: 'javaScript-performance-tips', body: 'We will look at 10 simple tips and tricks to increase the speed of your code when writing JS' },
    { title: 'Tailwind vs. Bootstrap', slug: 'tailwind-vs-bootstrap', body: 'Both Tailwind and Bootstrap are very popular CSS frameworks. In this article, we will compare them' },
    { title: 'Writing Great Unit Tests', slug: 'writing-great-unit-tests', body: 'We will look at 10 simple tips and tricks on writing unit tests in JavaScript' },
    { title: 'What Is New In PHP 8?', slug: 'what-is-new-in-PHP-8', body: 'In this article we will look at some of the new features offered in version 8 of PHP' },
  ];
}

Run the seed script to populate your database with initial data:

npx prisma db seed

Your database is now set up and seeded with initial data. You can proceed to use Prisma in your Remix project to perform CRUD operations on your data.

Step 6: Using Prisma in Remix

Create a utility file to manage your Prisma client instance. For example, create a app/utils/db.server.ts file:

import { PrismaClient } from "@prisma/client";

let db: PrismaClient;

declare global {
  var __db: PrismaClient | undefined;
}

if (process.env.NODE_ENV === 'production') {
  db = new PrismaClient();
  db.$connect();
} else {
  if (!global.__db) {
    global.__db = new PrismaClient();
    global.__db.$connect();
  }
  db = global.__db;
}

export { db };

This setup ensures that you have a single Prisma client instance throughout your application lifecycle, avoiding potential issues with multiple instances in a development environment.

Example Routes for Data Handling

Create some example routes to demonstrate how to use Prisma to fetch and display data in your Remix application.

app/routes/posts/index.jsx
import { Link, useLoaderData } from "@remix-run/react";
import { db } from '../../utils/db.server';

export const loader = async () => {
  const data = {
    posts: await db.post.findMany({
      take: 20,
      select: { slug: true, title: true, createdAt: true },
      orderBy: { createdAt: 'desc' }
    })
  };
  return data;
}

function PostItems() {
  const { posts } = useLoaderData();
  return (
    <>
      <div>
        <h1>Posts</h1>
        <Link to='/posts/new' className="btn btn-primary">New Post</Link>
      </div>
      <div className="row mt-3">
        {posts.map(post => (
          <div className="card mb-3 p-3" key={post.slug}>
            <Link to={`./${post.slug}`}>
              <h3 className="card-header">{post.title}</h3>
              {new Date(post.createdAt).toLocaleString()}
            </Link>
          </div>
        ))}
      </div>
    </>
  );
}

export default PostItems;
app/routes/posts/$postSlug.jsx
import { Link, useLoaderData } from "@remix-run/react";
import { db } from '../../utils/db.server';

export const loader = async ({ params }) => {
  const post = await db.post.findUnique({
    where: { slug: params.postSlug }
  });

  if (!post) throw new Error('Post not found');

  const data = { post };
  return data;
}

function Post() {
  const { post } = useLoaderData();
  return (
    <div className="card w-100">
      <div className="card-header">
        <h1>{post.title}</h1>
      </div>
      <div className="card-body">{post.body}</div>
      <div className="card-footer">
        <Link to='/posts' className="btn btn-danger">Back</Link>
      </div>
    </div>
  );
}

export default Post;
app/routes/posts/new.jsx
import { redirect } from "@remix-run/node";
import { Link } from "@remix-run/react";
import { db } from "../../utils/db.server";

export const action = async ({ request }) => {
  const form = await request.formData();
  const title = form.get("title");
  const body = form.get("body");
  const fields = { title, body, slug: title.split(' ').join('-') };

  const post = await db.post.create({ data: fields });
  return redirect(`/posts/${post.slug}`);
};

function NewPost() {
  return (
    <div className="card w-100">
      <form method="POST">
        <div className="card-header">
          <h1>New Post</h1>
          <Link to="/posts" className="btn btn-danger">Back</Link>
        </div>
        <div className="card-body">
          <div className="form-control my-2">
            <label htmlFor="title">Title</label>
            <input type="text" name="title" id="title" />
          </div>
          <div className="form-control my-2">
            <label htmlFor="body">Post Body</label>
            <textarea name="body" id="body" />
          </div>
        </div>
        <div className="card-footer">
          <button type="submit" className="btn btn-success">Add Post</button>
        </div>
      </form>
    </div>
  );
}

export default NewPost;

These routes provide examples of listing posts, displaying individual post details, and creating new posts using Prisma with Remix.

Conclusion

In this article, we've explored the process of bootstrapping a Remix project, from setting up the initial project to integrating a database using Prisma. Remix offers a streamlined and efficient approach to building server-rendered React applications, providing excellent performance and simplified data management.

While setting up a new project can seem daunting, following the steps outlined in this guide will help you get up and running quickly. Whether you've chosen Remix for its superior handling of dynamic content or its optimized server-side rendering, you're well on your way to creating a robust and scalable web application.

For managing your content seamlessly, consider using Wisp CMS. Wisp offers powerful tools and intuitive interfaces to help you manage and deliver content effectively within your Remix project. Check out the Wisp documentation for more information on integrating Wisp with your projects.

Further Reading:

Get engineers' time back from marketing!

Don't let managing a blog on your site get in the way of your core product.

Wisp empowers your marketing team to create and manage content on your website without consuming more engineering hours.

Get started in few lines of codes.

Choosing a CMS
Related Posts
How to Add a Blog to a Remix Project using Wisp CMS

How to Add a Blog to a Remix Project using Wisp CMS

Unleash the power of dynamic blogging! Discover how Wisp CMS and Remix Framework can streamline your content creation process with ease and performance.

Read Full Story
How to Add a Blog onto a Nuxt 3.12 App Using Wisp CMS

How to Add a Blog onto a Nuxt 3.12 App Using Wisp CMS

Let's add a blog to a Nuxt 3.12 application using Wisp CMS. This article will cover everything from setting up the project, integrating Wisp CMS, to deploying your Nuxt application.

Read Full Story
Setting up tRPC with Next.js 14

Setting up tRPC with Next.js 14

Unlock the full potential of Next.js 14 by integrating it with tRPC! Follow this comprehensive guide to create type-safe, efficient applications—perfect for developers looking to optimize.

Read Full Story