# Gatsby

> 하이드레이션 전에도 정적 HTML에서 이미 작동하고, 이후에는 fetch로 향상되는 Gatsby 폼. 시크릿 키로 POST하는 Gatsby Function도 함께 다룹니다.

Gatsby는 빌드 시점에 모든 페이지를 HTML로 렌더링합니다. 폼에 실제 `action`을 지정하면 React가 하이드레이션되기 전이나 번들 로드에 실패한 경우에도 POST되고, 하이드레이션된 뒤에는 컴포넌트가 `fetch`로 POST하고 결과를 그 자리에 보여 줍니다.

## 브라우저에서 POST하기

### 1단계: 폼 ID 추가하기

`.env.production`:

```bash
GATSBY_IKIFORM_FORM_ID=frm_xxxxxxxxxxxxxxxx
```

Gatsby는 `GATSBY_`로 시작하는 변수만 브라우저 코드에 노출하며, 빌드 시점에 인라인합니다. `gatsby develop`용으로 `.env.development`에도 같은 줄을 추가하세요.

### 2단계: 컴포넌트 추가하기

`src/components/contact-form.js`:

```jsx
import * as React from "react";

const endpoint = `https://ikiform.com/f/${process.env.GATSBY_IKIFORM_FORM_ID}`;

export default function ContactForm() {
  const [status, setStatus] = React.useState("idle");
  const [message, setMessage] = React.useState("");

  async function onSubmit(event) {
    event.preventDefault();
    setStatus("sending");
    try {
      const response = await fetch(endpoint, {
        method: "POST",
        headers: { Accept: "application/json" },
        body: new FormData(event.currentTarget),
      });
      const result = await response.json();
      if (result.ok) return setStatus("sent");
      setMessage(result.error.message);
    } catch {
      setMessage("Couldn't reach the server. Try again.");
    }
    setStatus("error");
  }

  if (status === "sent") return <p>Thanks, we got it.</p>;

  return (
    <form action={endpoint} method="post" onSubmit={onSubmit}>
      <label>
        Email
        <input name="email" type="email" autoComplete="email" required />
      </label>
      <label>
        Message
        <textarea name="message" rows={5} />
      </label>
      <input type="hidden" name="_redirect" value="https://example.com/thanks/" />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {status === "error" ? <p role="alert">{message}</p> : null}
      <button disabled={status === "sending"}>Send</button>
    </form>
  );
}
```

`src/pages/contact.js` 같은 아무 페이지에서 렌더링하고, JavaScript 없는 경우의 리디렉션용으로 `src/pages/thanks.js`를 추가하세요. 필드 오류와 SDK의 사람 확인은 [React 가이드](https://ikiform.com/ko/docs/guides/react.md#component)처럼 확장하세요.

## Gatsby Function에서 POST하기

Gatsby Function은 시크릿 키와 함께 POST를 전달할 수 있으며, 이 경우 허용 도메인과 사람 확인을 건너뜁니다. Gatsby 어댑터를 쓰는 Netlify처럼 Gatsby Functions를 지원하는 호스트에서만 실행되며, GitHub Pages 같은 순수 정적 호스트에서는 실행할 수 없습니다. `IKIFORM_FORM_ID`와 `submissions:write` 스코프의 시크릿 키를 `IKIFORM_SECRET_KEY`로 저장하되, 브라우저에 닿지 않도록 `GATSBY_` 접두사 없이 저장하세요.

`src/api/contact.js`:

```js
export default async function handler(req, res) {
  if (req.method !== "POST") return res.status(405).end();
  const response = await fetch(`https://ikiform.com/f/${process.env.IKIFORM_FORM_ID}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
      Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
    },
    body: JSON.stringify(req.body),
  });
  res.status(response.status).json(await response.json());
}
```

Gatsby는 JSON과 폼 인코딩 본문을 `req.body`로 파싱하므로, 페이지는 어느 형식으로든 `/api/contact`에 POST할 수 있습니다. 이 경우 모든 POST가 레이트 리밋에서 함수의 IP 주소를 공유합니다. [서버에서 보내기](https://ikiform.com/ko/docs/guides/server.md)를 참고하세요.

## Allowed domains

브라우저 POST에 적용됩니다. Gatsby Function에서 보내는 POST는 시크릿 키를 사용하므로 이 검사를 건너뜁니다.

| 폼이 실행되는 곳 | Allowed domains에 추가할 값 |
| --- | --- |
| `gatsby develop`과 `gatsby serve` | `localhost`. `http://localhost:8000`과 `http://localhost:9000`에 일치합니다. Ikiform은 포트가 아니라 호스트 이름만 비교합니다. |
| 여러분의 도메인 | `example.com`, `www.example.com`도 포함 |
| Netlify | `your-site.netlify.app`. 배포 미리보기(`deploy-preview-12--your-site.netlify.app`)와 브랜치 배포(`branch--your-site.netlify.app`)는 하위 도메인이 아니므로, 테스트하는 동안 정확한 미리보기 호스트를 추가하세요. |
| Cloudflare Pages | `your-project.pages.dev`. `abc123.your-project.pages.dev` 같은 미리보기 URL은 하위 도메인이므로 이미 일치합니다. |

- `netlify.app`이나 `pages.dev`만 단독으로 추가하지 마세요. 다른 사람의 사이트를 포함해 그 호스트의 모든 사이트가 일치하게 됩니다.

## 테스트 체크리스트

1. `gatsby build`를 실행하고 `public/contact/index.html`을 여세요. 폼의 `action`이 `undefined`가 아니라 여러분의 폼 ID로 끝나야 합니다.
2. 허용 도메인에 `localhost`를 넣고 `gatsby serve`를 실행해 제출하세요. 감사 메시지가 폼을 대체해야 합니다.
3. JavaScript를 끄고 새로 고친 뒤 제출하세요. 로컬에서는 Ikiform의 감사 페이지가, `https:` 도메인에서는 `/thanks/`가 보여야 합니다.
4. 함수를 쓴다면 배포 미리보기에서 `/api/contact`로 POST하고 Ikiform에 제출이 나타나는지 확인하세요.

## 문제 해결

| 증상 | 원인 | 해결 |
| --- | --- | --- |
| `404 not_found`, action이 `/f/undefined`로 끝남 | 변수에 `GATSBY_` 접두사가 없거나, 빌드 시점에 설정되지 않음 | `GATSBY_IKIFORM_FORM_ID`로 이름을 바꾸고 다시 빌드하세요. |
| 함수가 `403 forbidden`으로 응답함 | 시크릿 키가 다른 워크스페이스의 키이거나, 폐기되었거나, `submissions:write`가 없음 | 해당 스코프로 시크릿 키를 만드세요. |
| 배포 후 `/api/contact`가 `404`를 반환함 | 호스트가 정적 파일만 제공함 | Gatsby Functions를 지원하는 호스트에 배포하거나 브라우저 버전을 사용하세요. |
| 제출이 `origin_mismatch`와 함께 스팸 필터에 들어감 | POST한 페이지가 폼의 허용 도메인에 없음 | 위 표의 호스트를 추가하세요. **Reject**를 골랐다면 같은 POST가 대신 `403 origin_not_allowed`를 받습니다. |

## 관련 문서

- [React](https://ikiform.com/ko/docs/guides/react.md): 대기, 오류, 성공 상태.
- [서버에서 보내기](https://ikiform.com/ko/docs/guides/server.md): 시크릿 API 키로 POST합니다.
- [Allowed domains](https://ikiform.com/ko/docs/spam.md#allowed-domains): 출처를 비교하는 방식과 불일치 시 동작.
- [오류](https://ikiform.com/ko/docs/errors.md): 모든 오류 코드와 원인, 해결 방법.
