Axios en frameworks JS
Next.js, Gatsby, Astro… da igual. Es un POST HTTPS a /f/{TOKEN}. Axios solo lo hace más corto.
Install + la llamada
npm i axios
const res = await axios.post(
"https://denorly.com/f/TOKEN",
data, // objeto plano: { nombre, email, mensaje }
{ headers: { "Content-Type": "application/json", "Accept": "application/json" } }
);
res.data.success; // true
El {TOKEN} no es secreto: va en el cliente sin problema, es la auth por diseño. Igual no hace falta una API key. Si proxeas desde el servidor (más abajo) es para ocultarlo de la vista o meter lógica (rate-limit, honeypot, logging), no por seguridad de credenciales.
Por framework
Opción A: Client Component (postea directo)
"use client";
import { useState } from "react";
import axios from "axios";
const TOKEN = "8f3b2c1a-9d4e-4f7a-b6c2-1e5a7d9c0b3f";
export default function ContactForm() {
const [sent, setSent] = useState(false);
const [error, setError] = useState("");
async function onSubmit(e) {
e.preventDefault();
setError("");
const data = Object.fromEntries(new FormData(e.currentTarget));
try {
const res = await axios.post(`https://denorly.com/f/${TOKEN}`, data, {
headers: { "Content-Type": "application/json", "Accept": "application/json" },
});
if (res.data.success) setSent(true);
} catch (err) {
// Denorly devuelve el motivo en err.response.data.error
setError(err.response?.data?.error ?? "Error de red");
}
}
if (sent) return <p>¡Enviado!</p>;
return (
<form onSubmit={onSubmit}>
<input name="nombre" required />
<input name="email" type="email" required />
<textarea name="mensaje" />
{error && <p style={{ color: "red" }}>{error}</p>}
<button type="submit">Enviar</button>
</form>
);
}
Opción B: Route Handler proxy (App Router)
El cliente postea a /api/contact y el server reenvía. El TOKEN vive en una env var, no en el bundle.
// app/api/contact/route.js
import axios from "axios";
export async function POST(req) {
const data = await req.json();
try {
const res = await axios.post(
`https://denorly.com/f/${process.env.DENORLY_TOKEN}`,
data,
{ headers: { "Content-Type": "application/json", "Accept": "application/json" } }
);
return Response.json(res.data, { status: 200 });
} catch (err) {
const body = err.response?.data ?? { success: false, error: "upstream error" };
return Response.json(body, { status: err.response?.status ?? 502 });
}
}
O como Server Action: misma idea, marca el módulo con "use server" y haz el axios.post dentro de la action: el TOKEN nunca toca el cliente.
Gatsby es estático: el envío es client-side en un componente React. Idéntico patrón.
import React, { useState } from "react";
import axios from "axios";
const TOKEN = "8f3b2c1a-9d4e-4f7a-b6c2-1e5a7d9c0b3f";
export default function Contact() {
const [sent, setSent] = useState(false);
const [error, setError] = useState("");
const onSubmit = async (e) => {
e.preventDefault();
setError("");
const data = Object.fromEntries(new FormData(e.currentTarget));
try {
const res = await axios.post(`https://denorly.com/f/${TOKEN}`, data, {
headers: { "Content-Type": "application/json", "Accept": "application/json" },
});
if (res.data.success) setSent(true);
} catch (err) {
setError(err.response?.data?.error ?? "Error de red");
}
};
if (sent) return <p>¡Enviado!</p>;
return (
<form onSubmit={onSubmit}>
<input name="nombre" required />
<input name="email" type="email" required />
<textarea name="mensaje" />
{error && <p style={{ color: "red" }}>{error}</p>}
<button type="submit">Enviar</button>
</form>
);
}
¿Necesitas proxy? Usa Gatsby Functions (src/api/contact.js) con la misma lógica del Route Handler de Next.
Astro envía estático por defecto. Usa un client island (<script>) o un componente de framework con client:load.
Script island (sin framework, sin axios)
---
// src/components/Contact.astro
const TOKEN = "8f3b2c1a-9d4e-4f7a-b6c2-1e5a7d9c0b3f";
---
<form id="contact">
<input name="nombre" required />
<input name="email" type="email" required />
<textarea name="mensaje"></textarea>
<button type="submit">Enviar</button>
</form>
<script define:vars={{ TOKEN }}>
document.getElementById("contact").addEventListener("submit", async (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.target));
const res = await fetch(`https://denorly.com/f/${TOKEN}`, {
method: "POST",
headers: { "Content-Type": "application/json", "Accept": "application/json" },
body: JSON.stringify(data),
});
const json = await res.json();
if (json.success) e.target.replaceWith(document.createTextNode("¡Enviado!"));
else alert(json.error);
});
</script>
¿Prefieres React/Vue/Svelte? Importa el componente con <Contact client:load /> y usa axios igual que en Gatsby.
⚠ Gotchas
- Axios lanza en respuestas no-2xx. Lee el motivo en
err.response.data.error(no enerr.message). Status:402límite,403origen/recaptcha,404form inactivo,400validación. - CORS desde el navegador funciona: el endpoint envía cabeceras CORS. Pero si configuraste una allow-list de dominios en el form y tu origen no está, recibes
403 ORIGIN_BLOCKED. En planes free cualquier origen pasa. - No hay objeto de errores por campo.
errores un único string, no espereserrors.email.