Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure API to work on Vercel from Next SSR #41

Merged
merged 1 commit into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/api/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
fastapi==0.104.1
python-multipart==0.0.5
4 changes: 4 additions & 0 deletions apps/api/src/app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from fastapi import FastAPI

from routers import demo

app = FastAPI()

app.include_router(demo.router, prefix="/demo", tags=["demo"])


@app.get("/")
async def root() -> dict[str, str]:
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
if __name__ == "__main__":
uvicorn.run(
"app:app",
host="localhost",
log_level="info",
access_log=True,
use_colors=True,
Expand Down
17 changes: 17 additions & 0 deletions apps/api/src/routers/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Annotated, Union

from fastapi import APIRouter, Cookie, Form

router = APIRouter()


@router.get("/me")
async def me(username: Annotated[Union[str, None], Cookie()] = None) -> str:
"""Who are you?"""
return f"You are {username or 'nobody'}"


@router.post("/square")
async def square(value: Annotated[int, Form()]) -> int:
"""Calculate the square of the value."""
return value * value
25 changes: 23 additions & 2 deletions apps/site/next.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
// All server-side API requests are handled by lib/utils/api.ts
// but this rewrite still exists for locally testing native POST requests
const LOCAL_API_URL = "http://localhost:8000";

// When deployed on Vercel, this path acts like a rewrite in vercel.json
// Next.js would normally be unable to find the API endpoint
// but Vercel somehow steps in and makes the Serverless Function visible
const VERCEL_API_PATH = "/api/";

/** @type {import('next').NextConfig} */
const nextConfig = {}
const nextConfig = {
rewrites: async () => {
return [
{
source: "/api/:path*",
destination:
process.env.NODE_ENV === "development"
? `${LOCAL_API_URL}/:path*`
: VERCEL_API_PATH,
samderanova marked this conversation as resolved.
Show resolved Hide resolved
},
];
},
};

module.exports = nextConfig
module.exports = nextConfig;
1 change: 1 addition & 0 deletions apps/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@react-three/drei": "^9.88.14",
"@react-three/fiber": "^8.15.11",
"@types/three": "^0.158.2",
"axios": "^1.6.2",
"clsx": "^2.0.0",
"lucide-react": "^0.292.0",
"next": "13.5.6",
Expand Down
21 changes: 21 additions & 0 deletions apps/site/src/app/demo/Demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import api from "@/lib/utils/api";

async function getMessage(): Promise<string> {
const res = await api.get<string>("/demo/me");
return res.data;
}

async function Demo() {
const message = await getMessage();
return (
<div>
<p>Demo: {message}</p>
<form method="post" action="/api/demo/square">
<input type="number" required name="value" style={{ color: "black" }} />
<button type="submit">Submit</button>
</form>
</div>
);
}

export default Demo;
1 change: 1 addition & 0 deletions apps/site/src/app/demo/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as default } from "./Demo";
samderanova marked this conversation as resolved.
Show resolved Hide resolved
24 changes: 24 additions & 0 deletions apps/site/src/lib/utils/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import axios from "axios";
import { cookies } from "next/headers";

const LOCAL_API_URL = "http://localhost:8000";
const SERVER_HOST = process.env.NEXT_PUBLIC_VERCEL_URL;

// The Vercel Serverless Function for the API lives outside the scope of Next.js
// so the publicly deployed URL must be used instead of a rewrite
const api = axios.create({
baseURL: SERVER_HOST ? `https://${SERVER_HOST}/api/` : LOCAL_API_URL,
});

api.interceptors.request.use((config) => {
const cookieStore = cookies();

// Inject user's client-side cookies along with API request
const provided = config.headers.get("Cookie");
const newCookies = (provided ? `${provided}; ` : "") + cookieStore.toString();
config.headers.set("Cookie", newCookies);

return config;
});

export default api;
1 change: 0 additions & 1 deletion apps/site/vercel.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"buildCommand": "cd ../.. && turbo run build --filter={apps/site} && cd apps/site && ./copy-api.sh",
"rewrites": [{ "source": "/api/(.*)", "destination": "api/index.py" }],
"functions": {
"api/index.py": {
"memory": 512,
Expand Down
14 changes: 13 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading