encore-frontend

Connect React/Next.js apps to Encore.ts backends.

INSTALLATION
npx skills add https://github.com/encoredev/skills --skill encore-frontend
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$27

// frontend/src/client.ts is auto-generated

import Client from "./client";

const client = new Client("http://localhost:4000");

// Fully typed API calls

const user = await client.user.getUser({ id: "123" });

console.log(user.email);

const newUser = await client.user.createUser({

  email: "new@example.com",

  name: "New User",

});

React Example

// frontend/src/components/UserProfile.tsx

import { useState, useEffect } from "react";

import Client from "../client";

const client = new Client(import.meta.env.VITE_API_URL);

export function UserProfile({ userId }: { userId: string }) {

  const [user, setUser] = useState(null);

  const [loading, setLoading] = useState(true);

  const [error, setError] = useState(null);

  useEffect(() => {

    client.user.getUser({ id: userId })

      .then(setUser)

      .catch(setError)

      .finally(() => setLoading(false));

  }, [userId]);

  if (loading) return <div>Loading...</div>;

  if (error) return <div>Error: {error.message}</div>;

  return (

    <div>

      <h1>{user.name}</h1>

      <p>{user.email}</p>

    </div>

  );

}

React with TanStack Query

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";

import Client from "../client";

const client = new Client(import.meta.env.VITE_API_URL);

export function UserProfile({ userId }: { userId: string }) {

  const { data: user, isLoading, error } = useQuery({

    queryKey: ["user", userId],

    queryFn: () => client.user.getUser({ id: userId }),

  });

  if (isLoading) return <div>Loading...</div>;

  if (error) return <div>Error: {error.message}</div>;

  return <div>{user.name}</div>;

}

export function CreateUserForm() {

  const queryClient = useQueryClient();

  const mutation = useMutation({

    mutationFn: (data: { email: string; name: string }) =>

      client.user.createUser(data),

    onSuccess: () => {

      queryClient.invalidateQueries({ queryKey: ["users"] });

    },

  });

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {

    e.preventDefault();

    const formData = new FormData(e.currentTarget);

    mutation.mutate({

      email: formData.get("email") as string,

      name: formData.get("name") as string,

    });

  };

  return (

    <form onSubmit={handleSubmit}>

      <input name="email" type="email" required />

      <input name="name" required />

      <button type="submit" disabled={mutation.isPending}>

        {mutation.isPending ? "Creating..." : "Create User"}

      </button>

    </form>

  );

}

Next.js Server Components

// app/users/[id]/page.tsx

import Client from "@/lib/client";

const client = new Client(process.env.API_URL);

export default async function UserPage({ params }: { params: { id: string } }) {

  const user = await client.user.getUser({ id: params.id });

  return (

    <div>

      <h1>{user.name}</h1>

      <p>{user.email}</p>

    </div>

  );

}

CORS Configuration

Configure CORS in your encore.app file:

{

    "id": "my-app",

    "global_cors": {

        "allow_origins_with_credentials": [

            "http://localhost:3000",

            "https://myapp.com",

            "https://*.myapp.com"

        ]

    }

}

CORS Options

Option

Description

allow_origins_without_credentials

Origins allowed for non-credentialed requests (default: ["*"])

allow_origins_with_credentials

Origins allowed for credentialed requests (cookies, auth headers)

allow_headers

Additional request headers to allow

expose_headers

Additional response headers to expose

debug

Enable CORS debug logging

Authentication from Frontend

For authenticated requests, pass the Authorization header:

// Using fetch

const response = await fetch("http://localhost:4000/profile", {

  headers: {

    "Authorization": `Bearer ${token}`,

  },

});

// Or include credentials for cookie-based auth

const response = await fetch("http://localhost:4000/profile", {

  credentials: "include",

});

With TanStack Query, configure a default fetcher:

const queryClient = new QueryClient({

  defaultOptions: {

    queries: {

      queryFn: async ({ queryKey }) => {

        const response = await fetch(queryKey[0] as string, {

          headers: { Authorization: `Bearer ${getToken()}` },

        });

        if (!response.ok) throw new Error("Request failed");

        return response.json();

      },

    },

  },

});

Using Plain Fetch

If you prefer not to use the generated client:

async function getUser(id: string) {

  const response = await fetch(`http://localhost:4000/users/${id}`);

  if (!response.ok) {

    throw new Error(`HTTP error: ${response.status}`);

  }

  return response.json();

}

async function createUser(email: string, name: string) {

  const response = await fetch("http://localhost:4000/users", {

    method: "POST",

    headers: { "Content-Type": "application/json" },

    body: JSON.stringify({ email, name }),

  });

  if (!response.ok) {

    throw new Error(`HTTP error: ${response.status}`);

  }

  return response.json();

}

Environment Variables

# .env.local (Next.js)

NEXT_PUBLIC_API_URL=http://localhost:4000

# .env (Vite)

VITE_API_URL=http://localhost:4000

Guidelines

  • Use encore gen client to generate typed API clients
  • Regenerate the client when your API changes
  • Configure CORS in encore.app for production domains
  • Use allow_origins_with_credentials for authenticated requests
  • Include Authorization header for token-based auth
  • Use credentials: "include" for cookie-based auth
  • Use environment variables for API URLs (different per environment)
  • The generated client handles errors and types automatically
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card