New ✨Next.js 15 runtime is live - blazing-fast builds & edge rendering.Read the docs
🎸Framework Integration

Integrating MeshBase with Remix

Build performant, progressively-enhanced apps with Remix and MeshBase. Loaders, actions, nested routes—all the Remix patterns you need.

Why MeshBase + Remix?

🌐Web Standards First

Remix uses native fetch, FormData, and Web APIs. MeshBase integrates naturally with these standards.

⚡Server-Side by Default

Loaders run server-side. Fetch MeshBase content server-side for optimal performance and SEO.

🔄Progressive Enhancement

Works without JavaScript. MeshBase content renders server-side, then enhances with client-side interactions.

🎯Nested Routes

Parallel data loading with nested routes. Fetch different MeshBase content types in parallel efficiently.

What You'll Need

  • ✓Remix project (v2+)
  • ✓A MeshBase account with content types defined
  • ✓Your MeshBase API key

Quick Start

Fetch MeshBase content in Remix loaders.

1. Configure environment

Create .env:

MESHBASE_API_URL=https://api.meshbase.io/v1
MESHBASE_API_KEY=your-api-key-here

2. Create API client

Create app/lib/meshbase.server.ts:

// .server.ts ensures this only runs server-side
const API_URL = process.env.MESHBASE_API_URL;
const API_KEY = process.env.MESHBASE_API_KEY;

export interface BlogPost {
  id: string;
  title: string;
  excerpt: string;
  content: string;
  coverImage?: string;
  slug: string;
}

export async function fetchFromMeshBase<T>(endpoint: string): Promise<T> {
  const response = await fetch(`${API_URL}${endpoint}`, {
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
  });

  if (!response.ok) {
    throw new Response('Failed to fetch from MeshBase', {
      status: response.status
    });
  }

  const json = await response.json();
  return json.data;
}

export async function getBlogPosts(): Promise<BlogPost[]> {
  return fetchFromMeshBase('/blog-posts');
}

export async function getBlogPost(id: string): Promise<BlogPost> {
  return fetchFromMeshBase(`/blog-posts/${id}`);
}

3. Use in route loader

Create app/routes/blog._index.tsx:

import type { LoaderFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { useLoaderData, Link } from '@remix-run/react';
import { getBlogPosts } from '~/lib/meshbase.server';

export async function loader({ request }: LoaderFunctionArgs) {
  const posts = await getBlogPosts();
  return json({ posts });
}

export default function BlogIndex() {
  const { posts } = useLoaderData<typeof loader>();

  return (
    <div>
      <h1>Blog Posts</h1>
      
      <div className="blog-list">
        {posts.map(post => (
          <article key={post.id}>
            <h2>
              <Link to={`/blog/${post.slug || post.id}`}>
                {post.title}
              </Link>
            </h2>
            <p>{post.excerpt}</p>
          </article>
        ))}
      </div>
    </div>
  );
}
✨

You're Done!

Your Remix app now fetches MeshBase content server-side. Perfect SEO, fast initial load, progressively enhanced!

Dynamic Routes

Create dynamic blog post pages.

Create app/routes/blog.$slug.tsx:

import type { LoaderFunctionArgs, MetaFunction } from '@remix-run/node';
import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { getBlogPost } from '~/lib/meshbase.server';

export async function loader({ params }: LoaderFunctionArgs) {
  const post = await getBlogPost(params.slug!);
  return json({ post });
}

export const meta: MetaFunction<typeof loader> = ({ data }) => {
  return [
    { title: data?.post.title },
    { name: 'description', content: data?.post.excerpt }
  ];
};

export default function BlogPost() {
  const { post } = useLoaderData<typeof loader>();

  return (
    <article>
      <h1>{post.title}</h1>
      
      {post.coverImage && (
        <img src={post.coverImage} alt={post.title} />
      )}
      
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

Nested Routes & Parallel Loading

Load different MeshBase content types in parallel with nested routes.

Parent route

app/routes/blog.tsx (layout):

import { Outlet } from '@remix-run/react';

export default function BlogLayout() {
  return (
    <div>
      <header>
        <h1>My Blog</h1>
        {/* Sidebar, nav, etc. */}
      </header>
      
      <main>
        <Outlet /> {/* Child routes render here */}
      </main>
    </div>
  );
}

Child routes load in parallel

Both loaders run simultaneously:

  • blog.tsx - layout data
  • blog._index.tsx - blog post list
⚡

Waterfall Prevention

Remix loads nested routes in parallel. No request waterfalls—all MeshBase data fetches happen simultaneously!

Caching with Headers

Add cache control headers for better performance.

export async function loader({ request }: LoaderFunctionArgs) {
  const posts = await getBlogPosts();
  
  return json(
    { posts },
    {
      headers: {
        'Cache-Control': 'public, max-age=60, s-maxage=3600'
      }
    }
  );
}

Cache-Control explained:

  • max-age=60 - Browser cache for 60s
  • s-maxage=3600 - CDN cache for 1 hour

Actions (Creating Content)

Use Remix actions to create/update MeshBase content (admin key required).

import type { ActionFunctionArgs } from '@remix-run/node';
import { redirect } from '@remix-run/node';

export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData();
  
  const title = formData.get('title');
  const content = formData.get('content');

  // Create post via MeshBase API (use admin key!)
  await fetch(`${API_URL}/blog-posts`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ADMIN_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ title, content })
  });

  return redirect('/blog');
}

export default function NewPost() {
  return (
    <form method="post">
      <input name="title" required />
      <textarea name="content" required />
      <button type="submit">Create Post</button>
    </form>
  );
}
🔒

Admin Key Required

Creating/updating content requires your MeshBase admin API key. Never expose this in client-side code—keep it server-side only!

Next Steps