Api And Static Assets

A frontend that cannot talk to a backend is just a brochure -- here is how to wire up ESLint for JSX, proxy API requests, and serve images through Vite.

May 15, 20264 min read2 / 2

The JSX setup is done and the app renders in the browser. But it cannot talk to an API yet and the linter still does not understand JSX. Before adding any more features, both of those gaps need to close.

ESLint Needs a JSX Plugin

The ESLint config from the tooling post handles JavaScript. JSX adds a few extra rules that the base config does not cover, so install the official plugin:

Bash
npm install -D eslint-plugin-react

Then add it to eslint.config.mjs. The key sections to add are the plugin itself, the recommended rules, and one required setting that tells ESLint which React version you are using (without it, the version-gated rules cannot fire):

JavaScript
import reactPlugin from "eslint-plugin-react"; export default [ { plugins: { react: reactPlugin }, rules: { ...reactPlugin.configs.recommended.rules, "react/prop-types": 0, }, settings: { react: { version: "detect" }, }, }, // ... your other config objects ];

react/prop-types is turned off because TypeScript handles that job better. Everything else in recommended is worth keeping.

htmlFor, Not for

One rule you will feel immediately: HTML for attributes become htmlFor in JSX. The same rename logic as classclassName -- for is a reserved JavaScript keyword.

JSX
{/* wrong -- linter warning */} <label for="size">Size</label> {/* correct */} <label htmlFor="size">Size</label>

Self-Close Childless Tags

JSX requires every element with no children to be self-closed:

JSX
{/* wrong */} <input type="text"></input> {/* correct */} <input type="text" />

HTML is forgiving about this. JSX is not.

Uppercase for Components, Lowercase for HTML

JSX uses the case of the first letter to decide what something is. Lowercase means a native HTML element. Uppercase means a React component you wrote.

JSX
<div /> // native div <Pizza /> // your Pizza component

If you name a component with a lowercase letter and try to render it, React will try to render a native <pizza> element, which does not exist. The linter will warn you, but the rule is useful to have internalized anyway.

The API Server

The Padre Gino's project ships with a small Fastify server at api/index.js. Start it alongside Vite:

Bash
node api/index.js

The server listens on localhost:3000 and exposes pizza data, types, and an order endpoint. The Vite dev server runs on a different port (usually 5173). If the browser tries to fetch /api/pizzas directly, it will hit port 5173 and get nothing because Vite does not know about that route.

Fixing the CORS Problem With a Proxy

Vite has a built-in proxy that solves this cleanly. Add it to vite.config.js:

JavaScript
import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], server: { proxy: { "/api": "http://localhost:3000", "/public": "http://localhost:3000", }, }, });

Vite dev server proxies /api and /public requests to the Fastify backend on port 3000 ExpandVite dev server proxies /api and /public requests to the Fastify backend on port 3000

Now any fetch to /api/pizzas in the browser gets forwarded to http://localhost:3000/api/pizzas. The browser never sees the cross-origin request because Vite handles it server-to-server. In production, the real web server (Nginx, a CDN, whatever) takes over this proxy role.

The /public proxy matters for images. The API serves pizza images from localhost:3000/public/. With this rule in place, an <img src="/public/pepperoni.jpg" /> in the browser will resolve correctly through the Vite proxy.

Static Assets

CSS global styles live in style.css at the project root. Add a <link> to index.html -- not in a component, but in the HTML file that bootstraps the whole app:

HTML
<link rel="stylesheet" href="/style.css" />

Vite serves files from the project root, so the path just works.

Images loaded dynamically (from the API) come through the proxy as described above. Images that are part of the app itself (a logo, a background) can go in the public/ folder that Vite creates, and you reference them with a root-relative path like /logo.svg.

With the API reachable and assets serving correctly, the next step is building the Order form -- a controlled component with a select and radio buttons that will eventually pull live data from that very API.

The Essentials

  1. eslint-plugin-react handles JSX-specific linting. The three rules that come up most: htmlFor not for, self-close childless tags, uppercase component names.
  2. react/prop-types: 0 disables the prop-types rule. TypeScript handles that. settings.react.version: "detect" is required or version-gated rules will not fire.
  3. Vite proxy routes /api and /public to the backend during development. No CORS issues because the proxy is server-to-server. In production, configure the same routes on the real web server.
  4. Global CSS lives in index.html via a <link> tag, not inside a component.

Further Reading and Watching