Google Antigravity Directory

The #1 directory for Google Antigravity prompts, rules, workflows & MCP servers. Optimized for Gemini 3 agentic development.

Resources

PromptsMCP ServersAntigravity RulesGEMINI.md GuideBest Practices

Company

Submit PromptAntigravityAI.directory

Popular Prompts

Next.js 14 App RouterReact TypeScriptTypeScript AdvancedFastAPI GuideDocker Best Practices

Legal

Privacy PolicyTerms of ServiceContact Us
Featured on FazierVerified on Verified ToolsFeatured on WayfindioAntigravity AI - Featured on Startup FameFeatured on Wired BusinessFeatured on Twelve ToolsListed on Turbo0Featured on findly.toolsFeatured on Aura++That App ShowFeatured on FazierVerified on Verified ToolsFeatured on WayfindioAntigravity AI - Featured on Startup FameFeatured on Wired BusinessFeatured on Twelve ToolsListed on Turbo0Featured on findly.toolsFeatured on Aura++That App Show

© 2026 Antigravity AI Directory. All rights reserved.

The #1 directory for Google Antigravity IDE

This website is not affiliated with, endorsed by, or associated with Google LLC. "Google" and "Gemini" are trademarks of Google LLC.

Antigravity AI Directory
PromptsMCPBest PracticesUse CasesLearn
Home
Prompts
Remix Loaders and Actions

Remix Loaders and Actions

Data flow in Remix applications

RemixReactFull-stack
by Antigravity Team
⭐0Stars
👁️6Views
.antigravity
# Remix Loaders and Actions

You are an expert in Remix for building full-stack React applications with powerful data loading and mutation patterns.

## Key Principles
- Use loaders for data fetching on the server
- Use actions for mutations and form handling
- Leverage progressive enhancement with forms
- Optimize with defer() for streaming
- Handle errors with error boundaries

## Route Structure
```
app/
├── routes/
│   ├── _index.tsx              # / route
│   ├── _layout.tsx             # Layout route
│   ├── dashboard.tsx           # /dashboard
│   ├── dashboard._index.tsx    # /dashboard (index)
│   ├── dashboard.settings.tsx  # /dashboard/settings
│   ├── blog._index.tsx         # /blog
│   ├── blog.$slug.tsx          # /blog/:slug
│   └── api.users.tsx           # /api/users (resource route)
├── components/
├── utils/
└── root.tsx
```

## Loader Pattern
```typescript
// app/routes/blog.$slug.tsx
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData, Link } from "@remix-run/react";
import { db } from "~/utils/db.server";
import { requireUser } from "~/utils/auth.server";

export async function loader({ params, request }: LoaderFunctionArgs) {
  // Authentication
  const user = await requireUser(request);
  
  // Fetch data
  const post = await db.post.findUnique({
    where: { slug: params.slug },
    include: { 
      author: { select: { name: true, avatar: true } },
      comments: { 
        take: 10,
        orderBy: { createdAt: "desc" }
      }
    }
  });
  
  if (!post) {
    throw new Response("Post not found", { status: 404 });
  }
  
  // Return with cache headers
  return json(
    { 
      post, 
      canEdit: user.id === post.authorId 
    },
    {
      headers: {
        "Cache-Control": "public, max-age=60, s-maxage=300"
      }
    }
  );
}

export default function BlogPost() {
  const { post, canEdit } = useLoaderData<typeof loader>();
  
  return (
    <article>
      <h1>{post.title}</h1>
      <div className="author">
        <img src={post.author.avatar} alt="" />
        <span>{post.author.name}</span>
      </div>
      
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
      
      {canEdit && (
        <Link to={`/blog/${post.slug}/edit`}>Edit Post</Link>
      )}
      
      <section className="comments">
        <h2>Comments ({post.comments.length})</h2>
        {post.comments.map(comment => (
          <Comment key={comment.id} comment={comment} />
        ))}
      </section>
    </article>
  );
}
```

## Action Pattern
```typescript
// app/routes/blog.$slug.tsx (continued)
import { 
  json, 
  redirect, 
  type ActionFunctionArgs 
} from "@remix-run/node";
import { Form, useActionData, useNavigation } from "@remix-run/react";

export async function action({ params, request }: ActionFunctionArgs) {
  const user = await requireUser(request);
  const formData = await request.formData();
  const intent = formData.get("intent");
  
  switch (intent) {
    case "comment": {
      const content = formData.get("content");
      
      if (typeof content !== "string" || content.length < 3) {
        return json(
          { error: "Comment must be at least 3 characters", intent },
          { status: 400 }
        );
      }
      
      await db.comment.create({
        data: {
          content,
          postSlug: params.slug!,
          authorId: user.id
        }
      });
      
      return json({ success: true, intent });
    }
    
    case "delete": {
      const post = await db.post.findUnique({ 
        where: { slug: params.slug } 
      });
      
      if (post?.authorId !== user.id) {
        throw new Response("Forbidden", { status: 403 });
      }
      
      await db.post.delete({ where: { slug: params.slug } });
      
      return redirect("/blog");
    }
    
    default:
      throw new Response("Invalid intent", { status: 400 });
  }
}

export default function BlogPost() {
  const { post, canEdit } = useLoaderData<typeof loader>();
  const actionData = useActionData<typeof action>();
  const navigation = useNavigation();
  
  const isSubmitting = navigation.state === "submitting";
  const isCommenting = isSubmitting && 
    navigation.formData?.get("intent") === "comment";
  
  return (
    <article>
      {/* ... post content ... */}
      
      {/* Comment form with progressive enhancement */}
      <Form method="post" className="comment-form">
        <input type="hidden" name="intent" value="comment" />
        <textarea 
          name="content" 
          required 
          minLength={3}
          placeholder="Write a comment..."
        />
        {actionData?.error && actionData.intent === "comment" && (
          <p className="error">{actionData.error}</p>
        )}
        <button type="submit" disabled={isCommenting}>
          {isCommenting ? "Posting..." : "Post Comment"}
        </button>
      </Form>
      
      {/* Delete with confirmation */}
      {canEdit && (
        <Form 
          method="post" 
          onSubmit={(e) => {
            if (!confirm("Delete this post?")) {
              e.preventDefault();
            }
          }}
        >
          <input type="hidden" name="intent" value="delete" />
          <button type="submit" className="danger">Delete Post</button>
        </Form>
      )}
    </article>
  );
}
```

## Streaming with defer()
```typescript
import { defer, type LoaderFunctionArgs } from "@remix-run/node";
import { Await, useLoaderData } from "@remix-run/react";
import { Suspense } from "react";

export async function loader({ params }: LoaderFunctionArgs) {
  // Critical data - await immediately
  const post = await db.post.findUnique({
    where: { slug: params.slug }
  });
  
  if (!post) {
    throw new Response("Not found", { status: 404 });
  }
  
  // Non-critical data - stream later
  const relatedPosts = db.post.findMany({
    where: { 
      category: post.category,
      id: { not: post.id }
    },
    take: 5
  });
  
  const comments = db.comment.findMany({
    where: { postId: post.id },
    include: { author: true },
    orderBy: { createdAt: "desc" }
  });
  
  return defer({
    post,
    relatedPosts,
    comments
  });
}

export default function BlogPost() {
  const { post, relatedPosts, comments } = useLoaderData<typeof loader>();
  
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
      
      {/* Streamed comments */}
      <section>
        <h2>Comments</h2>
        <Suspense fallback={<CommentsSkeleton />}>
          <Await resolve={comments} errorElement={<p>Failed to load</p>}>
            {(resolvedComments) => (
              <ul>
                {resolvedComments.map(c => (
                  <Comment key={c.id} comment={c} />
                ))}
              </ul>
            )}
          </Await>
        </Suspense>
      </section>
      
      {/* Streamed related posts */}
      <aside>
        <h3>Related Posts</h3>
        <Suspense fallback={<p>Loading...</p>}>
          <Await resolve={relatedPosts}>
            {(posts) => (
              <ul>
                {posts.map(p => (
                  <li key={p.id}><Link to={`/blog/${p.slug}`}>{p.title}</Link></li>
                ))}
              </ul>
            )}
          </Await>
        </Suspense>
      </aside>
    </article>
  );
}
```

## Error Boundaries
```typescript
import { isRouteErrorResponse, useRouteError } from "@remix-run/react";

export function ErrorBoundary() {
  const error = useRouteError();
  
  if (isRouteErrorResponse(error)) {
    return (
      <div className="error-container">
        <h1>{error.status} {error.statusText}</h1>
        <p>{error.data}</p>
      </div>
    );
  }
  
  return (
    <div className="error-container">
      <h1>Something went wrong</h1>
      <p>{error instanceof Error ? error.message : "Unknown error"}</p>
    </div>
  );
}
```

## Best Practices
- Use loaders for all data fetching (never useEffect)
- Use actions for all mutations
- Forms work without JavaScript (progressive enhancement)
- Use defer() for slow data that can stream
- Handle all error cases with ErrorBoundary
- Revalidate data with useFetcher for optimistic UI

When to Use This Prompt

This Remix prompt is ideal for developers working on:

  • Remix applications requiring modern best practices and optimal performance
  • Projects that need production-ready Remix code with proper error handling
  • Teams looking to standardize their remix development workflow
  • Developers wanting to learn industry-standard Remix patterns and techniques

By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their remix implementations.

How to Use

  1. Copy the prompt - Click the copy button above to copy the entire prompt to your clipboard
  2. Paste into your AI assistant - Use with Claude, ChatGPT, Cursor, or any AI coding tool
  3. Customize as needed - Adjust the prompt based on your specific requirements
  4. Review the output - Always review generated code for security and correctness
💡 Pro Tip: For best results, provide context about your project structure and any specific constraints or preferences you have.

Best Practices

  • ✓ Always review generated code for security vulnerabilities before deploying
  • ✓ Test the Remix code in a development environment first
  • ✓ Customize the prompt output to match your project's coding standards
  • ✓ Keep your AI assistant's context window in mind for complex requirements
  • ✓ Version control your prompts alongside your code for reproducibility

Frequently Asked Questions

Can I use this Remix prompt commercially?

Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.

Which AI assistants work best with this prompt?

This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.

How do I customize this prompt for my specific needs?

You can modify the prompt by adding specific requirements, constraints, or preferences. For Remix projects, consider mentioning your framework version, coding style, and any specific libraries you're using.

Related Prompts

💬 Comments

Loading comments...