diff --git a/.env.example b/.env.example
index 44971291..b09d157b 100644
--- a/.env.example
+++ b/.env.example
@@ -9,9 +9,6 @@ GOOGLE_CLIENT_SECRET=
AUTH_COOKIE_NAME=app.session-token
-# openssl rand -base64 32
-AUTH_SECRET=xxx
-
OUR_DOMAIN=localhost:3000
S3_BUCKET_NAME=xxx
@@ -19,6 +16,7 @@ S3_REGION=xxx
S3_ACCESS_KEY_ID=xxx
S3_SECRET_ACCESS_KEY=xxx
S3_ENDPOINT=s3.us-west-004.backblazeb2.com
+S3_CDN_PREFIX=xxx
MAILGUN_APIKEY=xxx
MAILGUN_DOMAIN=xxx
diff --git a/app/components/main/MainLayout.tsx b/app/components/main/MainLayout.tsx
index 8c49355c..6c653f96 100644
--- a/app/components/main/MainLayout.tsx
+++ b/app/components/main/MainLayout.tsx
@@ -1,14 +1,13 @@
import { Link } from "@remix-run/react"
import { logout } from "~/lib/auth.client"
+import { APP_NAME } from "~/lib/env"
import { useStore } from "~/lib/store"
export function MainLayout({
isLoggedIn,
- appName,
children,
}: {
isLoggedIn: boolean
- appName: string
children: React.ReactNode
}) {
const setLoginModalOpened = useStore((store) => store.setLoginModalOpened)
@@ -42,7 +41,7 @@ export function MainLayout({
- {appName}
+ {APP_NAME}
diff --git a/app/components/site/SiteLayout.tsx b/app/components/site/SiteLayout.tsx
index 93e3762d..493d5dda 100644
--- a/app/components/site/SiteLayout.tsx
+++ b/app/components/site/SiteLayout.tsx
@@ -9,7 +9,8 @@ import { SubscribeModal } from "../common/SubscribeModal"
import { Avatar } from "../ui/Avatar"
import { Button } from "../ui/Button"
import { UniLink } from "../ui/UniLink"
-import { IS_PROD, OUR_DOMAIN } from "~/lib/config.shared"
+import { IS_PROD } from "~/lib/constants"
+import { OUR_DOMAIN } from "~/lib/env"
type MenuLink = {
text: string
diff --git a/app/lib/auth.server.ts b/app/lib/auth.server.ts
index f7234458..12ebd3dd 100644
--- a/app/lib/auth.server.ts
+++ b/app/lib/auth.server.ts
@@ -1,9 +1,9 @@
import type { User, Membership } from "@prisma/client"
-import { AUTH_COOKIE_NAME } from "./config.server"
import { prismaRead } from "./db.server"
import dayjs from "dayjs"
import { type CookieSerializeOptions, createCookie } from "@remix-run/node"
-import { IS_PROD, OUR_DOMAIN } from "./config.shared"
+import { AUTH_COOKIE_NAME, OUR_DOMAIN } from "./env"
+import { IS_PROD } from "./constants"
export type AuthUser = User & {
memberships: Membership[]
diff --git a/app/lib/config.server.ts b/app/lib/config.server.ts
deleted file mode 100644
index bb0a93fd..00000000
--- a/app/lib/config.server.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export const AUTH_COOKIE_NAME = process.env.AUTH_COOKIE_NAME
-export const S3_REGION = process.env.S3_REGION
-export const S3_ACCESS_KEY_ID = process.env.S3_ACCESS_KEY_ID
-export const S3_SECRET_ACCESS_KEY = process.env.S3_SECRET_ACCESS_KEY
-
-export const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME
-export const S3_ENDPOINT = process.env.S3_ENDPOINT
-export const MAILGUN_APIKEY = process.env.MAILGUN_APIKEY
-export const MAILGUN_DOMAIN = process.env.MAILGUN_DOMAIN
-export const MAILGUN_EU = process.env.MAILGUN_EU
-// Primary Fly region
-export const PRIMARY_REGION = process.env.PRIMARY_REGION
-// Curreny Fly region
-export const FLY_REGION = process.env.FLY_REGION
-export const IS_PRIMARY_REGION = Boolean(
- FLY_REGION && PRIMARY_REGION === FLY_REGION
-)
diff --git a/app/lib/config.shared.ts b/app/lib/constants.ts
similarity index 50%
rename from app/lib/config.shared.ts
rename to app/lib/constants.ts
index 2fd15595..b4e20c70 100644
--- a/app/lib/config.shared.ts
+++ b/app/lib/constants.ts
@@ -1,12 +1,9 @@
import { description } from "../../package.json"
-export const IS_BROWSER = typeof window !== "undefined"
+export const IS_BROWSER = typeof document !== "undefined"
export const IS_PROD = IS_BROWSER
? !location.hostname.endsWith("localhost")
: process.env.NODE_ENV === "production"
-export const APP_NAME = IS_BROWSER ? ENV.APP_NAME : process.env.APP_NAME
-export const OUR_DOMAIN = IS_BROWSER ? ENV.OUR_DOMAIN : process.env.OUR_DOMAIN
-
export const APP_DESCRIPTION = description
diff --git a/app/lib/db.server.ts b/app/lib/db.server.ts
index 4b9ecc5e..b16fefff 100644
--- a/app/lib/db.server.ts
+++ b/app/lib/db.server.ts
@@ -1,6 +1,6 @@
import { PrismaClient } from "@prisma/client"
-import { IS_PRIMARY_REGION } from "./config.server"
-import { IS_PROD } from "./config.shared"
+import { IS_PROD } from "./constants"
+import { isPrimaryRegion } from "./env"
import { singleton } from "./singleton.server"
export const prismaWrite = /* @__PURE__ */ singleton(
@@ -12,7 +12,7 @@ export const prismaWrite = /* @__PURE__ */ singleton(
export const prismaRead = /* @__PURE__ */ singleton("prisma-read", () => {
// 5433 is the read-replica port
let url = process.env.DATABASE_URL
- if (!IS_PRIMARY_REGION && IS_PROD) {
+ if (!isPrimaryRegion() && IS_PROD) {
url = url.replace(":5432", ":5433")
}
console.log("read replica url", url)
diff --git a/app/lib/env.ts b/app/lib/env.ts
new file mode 100644
index 00000000..bdadfe8f
--- /dev/null
+++ b/app/lib/env.ts
@@ -0,0 +1,40 @@
+import { IS_BROWSER } from "./constants"
+
+/**
+ * Can be called in browser and server
+ * only exposing env variables that're available in browser
+ */
+export const getCommonEnv = (
+ key: T
+): BrowserEnv[T] => {
+ return IS_BROWSER ? ENV[key] : process.env[key]
+}
+
+export const getServerEnv = (
+ key: T
+): ServerEnv[T] => {
+ if (IS_BROWSER) throw new Error(`getServerEnv() called in browser`)
+ return process.env[key]
+}
+
+export const isPrimaryRegion = () => {
+ const FLY_REGION = getServerEnv("FLY_REGION")
+ const PRIMARY_REGION = getServerEnv("PRIMARY_REGION")
+ return Boolean(FLY_REGION && PRIMARY_REGION === FLY_REGION)
+}
+
+// Use /* @__PURE__ */ annotation to make tree-shaking work
+export const AUTH_COOKIE_NAME = /* @__PURE__ */ getServerEnv("AUTH_COOKIE_NAME")
+export const APP_NAME = /* @__PURE__ */ getCommonEnv("APP_NAME")
+export const OUR_DOMAIN = /* @__PURE__ */ getCommonEnv("OUR_DOMAIN")
+export const S3_CDN_PREFIX = /* @__PURE__ */ getCommonEnv("S3_CDN_PREFIX")
+export const S3_REGION = /* @__PURE__ */ getServerEnv("S3_REGION")
+export const S3_ACCESS_KEY_ID = /* @__PURE__ */ getServerEnv("S3_ACCESS_KEY_ID")
+export const S3_SECRET_ACCESS_KEY = /* @__PURE__ */ getServerEnv(
+ "S3_SECRET_ACCESS_KEY"
+)
+export const S3_BUCKET_NAME = /* @__PURE__ */ getServerEnv("S3_BUCKET_NAME")
+export const S3_ENDPOINT = /* @__PURE__ */ getServerEnv("S3_ENDPOINT")
+export const MAILGUN_APIKEY = /* @__PURE__ */ getServerEnv("MAILGUN_APIKEY")
+export const MAILGUN_DOMAIN = /* @__PURE__ */ getServerEnv("MAILGUN_DOMAIN")
+export const MAILGUN_EU = /* @__PURE__ */ getServerEnv("MAILGUN_EU")
diff --git a/app/lib/helpers.ts b/app/lib/helpers.ts
index cd0fb15d..e94b111a 100644
--- a/app/lib/helpers.ts
+++ b/app/lib/helpers.ts
@@ -1,4 +1,5 @@
-import { IS_PROD, OUR_DOMAIN } from "./config.shared"
+import { IS_PROD } from "./constants"
+import { OUR_DOMAIN } from "./env"
export const getSiteLink = ({ subdomain }: { subdomain: string }) => {
return `${IS_PROD ? "https" : "http"}://${subdomain}.${OUR_DOMAIN}`
diff --git a/app/lib/mailgun.server.ts b/app/lib/mailgun.server.ts
index a571b2b8..13deafa0 100644
--- a/app/lib/mailgun.server.ts
+++ b/app/lib/mailgun.server.ts
@@ -1,9 +1,15 @@
import Mailgun from "mailgun.js"
import FormData from "form-data"
import { singleton } from "./singleton.server"
-import { MAILGUN_APIKEY, MAILGUN_DOMAIN, MAILGUN_EU } from "~/lib/config.server"
+import {
+ APP_NAME,
+ MAILGUN_APIKEY,
+ MAILGUN_DOMAIN,
+ MAILGUN_EU,
+ OUR_DOMAIN,
+} from "~/lib/env"
import type { MailgunMessageData } from "mailgun.js/interfaces/Messages"
-import { APP_NAME, IS_PROD, OUR_DOMAIN } from "./config.shared"
+import { IS_PROD } from "./constants"
const getClient = () =>
singleton("mailgun", () => {
diff --git a/app/lib/s3.server.ts b/app/lib/s3.server.ts
index b9d135ea..6a33cb7a 100644
--- a/app/lib/s3.server.ts
+++ b/app/lib/s3.server.ts
@@ -4,7 +4,7 @@ import {
S3_SECRET_ACCESS_KEY,
S3_BUCKET_NAME,
S3_ENDPOINT,
-} from "~/lib/config.server"
+} from "~/lib/env"
import {
S3Client,
PutObjectCommand,
diff --git a/app/lib/tenant.server.ts b/app/lib/tenant.server.ts
index fcae2272..07935ad7 100644
--- a/app/lib/tenant.server.ts
+++ b/app/lib/tenant.server.ts
@@ -1,4 +1,4 @@
-import { OUR_DOMAIN } from "./config.shared"
+import { OUR_DOMAIN } from "./env"
export const getTenant = (request: Request) => {
const host = request.headers.get("host")
diff --git a/app/lib/user-contents.ts b/app/lib/user-contents.ts
index 2ba430ea..80196587 100644
--- a/app/lib/user-contents.ts
+++ b/app/lib/user-contents.ts
@@ -1,4 +1,5 @@
-import { IS_PROD, OUR_DOMAIN } from "./config.shared"
+import { IS_PROD } from "./constants"
+import { S3_CDN_PREFIX } from "./env"
export function getUserContentsUrl(filename: string): string
export function getUserContentsUrl(filename: undefined | null): undefined
@@ -8,7 +9,7 @@ export function getUserContentsUrl(
export function getUserContentsUrl(filename: string | undefined | null) {
if (!filename) return undefined
if (IS_PROD) {
- return `https://usercontents.${OUR_DOMAIN}/${filename}`
+ return `${S3_CDN_PREFIX}/${filename}`
}
return `/dev-s3-proxy?${new URLSearchParams({
filename: filename,
diff --git a/app/root.tsx b/app/root.tsx
index 0dede57b..8556d41f 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -6,7 +6,6 @@ import {
} from "@remix-run/node"
import {
Links,
- LiveReload,
Meta,
Outlet,
Scripts,
@@ -17,7 +16,7 @@ import LoginModal from "~/components/common/LoginModal"
import { createStore, StoreProvider } from "./lib/store"
import { Toaster } from "react-hot-toast"
import css from "./generated.css"
-import { APP_NAME } from "./lib/config.shared"
+import { APP_NAME } from "./lib/env"
export const meta: MetaFunction = () => {
return {
@@ -31,13 +30,14 @@ export const links: LinksFunction = () => {
return [{ href: css, rel: "stylesheet", type: "text/css" }]
}
-type LoaderData = { ENV: Record }
+type LoaderData = { ENV: BrowserEnv }
export const loader: LoaderFunction = async () => {
return json({
ENV: {
APP_NAME: process.env.APP_NAME,
OUR_DOMAIN: process.env.OUR_DOMAIN,
+ S3_CDN_PREFIX: process.env.S3_CDN_PREFIX,
},
})
}
diff --git a/app/routes/api/login-complete.ts b/app/routes/api/login-complete.ts
index 47de4bf9..2ba1b5f5 100644
--- a/app/routes/api/login-complete.ts
+++ b/app/routes/api/login-complete.ts
@@ -1,8 +1,9 @@
import { type LoaderFunction, redirect } from "@remix-run/node"
import { z } from "zod"
import { generateCookie } from "~/lib/auth.server"
-import { IS_PROD, OUR_DOMAIN } from "~/lib/config.shared"
+import { IS_PROD } from "~/lib/constants"
import { prismaRead, prismaWrite } from "~/lib/db.server"
+import { OUR_DOMAIN } from "~/lib/env"
export const loader: LoaderFunction = async ({ request }) => {
const url = new URL(request.url)
diff --git a/app/routes/api/login.ts b/app/routes/api/login.ts
index 0a9f962e..05ee24e1 100644
--- a/app/routes/api/login.ts
+++ b/app/routes/api/login.ts
@@ -4,8 +4,9 @@ import { prismaRead, prismaWrite } from "~/lib/db.server"
import { nanoid } from "nanoid"
import UAParser from "ua-parser-js"
import dayjs from "dayjs"
-import { IS_PROD, OUR_DOMAIN } from "~/lib/config.shared"
import { generateCookie } from "~/lib/auth.server"
+import { OUR_DOMAIN } from "~/lib/env"
+import { IS_PROD } from "~/lib/constants"
export const loader: LoaderFunction = async ({ request }) => {
const url = new URL(request.url)
diff --git a/app/routes/api/request-login-link.ts b/app/routes/api/request-login-link.ts
index 8cd44d29..77492328 100644
--- a/app/routes/api/request-login-link.ts
+++ b/app/routes/api/request-login-link.ts
@@ -1,8 +1,9 @@
-import { ActionFunction, redirect } from "@remix-run/node"
+import { ActionFunction } from "@remix-run/node"
import dayjs from "dayjs"
import { z } from "zod"
-import { IS_PROD, OUR_DOMAIN } from "~/lib/config.shared"
+import { IS_PROD } from "~/lib/constants"
import { prismaWrite } from "~/lib/db.server"
+import { OUR_DOMAIN } from "~/lib/env"
import { sendLoginEmail } from "~/lib/mailgun.server"
export const action: ActionFunction = async ({ request }) => {
diff --git a/app/routes/dashboard/$subdomain/settings/domains.tsx b/app/routes/dashboard/$subdomain/settings/domains.tsx
index 3b916234..f2a59670 100644
--- a/app/routes/dashboard/$subdomain/settings/domains.tsx
+++ b/app/routes/dashboard/$subdomain/settings/domains.tsx
@@ -6,7 +6,7 @@ import { Button } from "~/components/ui/Button"
import { Input } from "~/components/ui/Input"
import { siteController } from "~/controllers/site.controller"
import { getAuthUser } from "~/lib/auth.server"
-import { OUR_DOMAIN } from "~/lib/config.shared"
+import { OUR_DOMAIN } from "~/lib/env"
export const loader: LoaderFunction = async ({ request, params }) => {
await getAuthUser(request, true)
diff --git a/app/routes/dashboard/new-site.tsx b/app/routes/dashboard/new-site.tsx
index 26ea1a8c..b4206f1a 100644
--- a/app/routes/dashboard/new-site.tsx
+++ b/app/routes/dashboard/new-site.tsx
@@ -10,7 +10,7 @@ import { Button } from "~/components/ui/Button"
import { Input } from "~/components/ui/Input"
import { siteController } from "~/controllers/site.controller"
import { getAuthUser } from "~/lib/auth.server"
-import { OUR_DOMAIN } from "~/lib/config.shared"
+import { OUR_DOMAIN } from "~/lib/env"
export const loader: LoaderFunction = async ({ request }) => {
const user = await getAuthUser(request, true)
diff --git a/app/routes/dev-s3-proxy.ts b/app/routes/dev-s3-proxy.ts
index 2265ff78..d89f2429 100644
--- a/app/routes/dev-s3-proxy.ts
+++ b/app/routes/dev-s3-proxy.ts
@@ -1,6 +1,6 @@
-import { LoaderFunction } from "@remix-run/node"
-import { S3_BUCKET_NAME, S3_ENDPOINT } from "~/lib/config.server"
-import { IS_PROD } from "~/lib/config.shared"
+import { type LoaderFunction } from "@remix-run/node"
+import { IS_PROD } from "~/lib/constants"
+import { S3_BUCKET_NAME, S3_ENDPOINT } from "~/lib/env"
export const loader: LoaderFunction = ({ request }) => {
if (IS_PROD) {
diff --git a/app/routes/index.tsx b/app/routes/index.tsx
index cc6b90fd..7c83b396 100644
--- a/app/routes/index.tsx
+++ b/app/routes/index.tsx
@@ -5,7 +5,8 @@ import { SiteHome } from "~/components/site/SiteHome"
import { SiteLayout, type SiteLayoutProps } from "~/components/site/SiteLayout"
import { siteController } from "~/controllers/site.controller"
import { getAuthUser } from "~/lib/auth.server"
-import { APP_DESCRIPTION, APP_NAME } from "~/lib/config.shared"
+import { APP_DESCRIPTION } from "~/lib/constants"
+import { APP_NAME } from "~/lib/env"
import { getTenant } from "~/lib/tenant.server"
import { PageVisibilityEnum, type PostOnSiteHome } from "~/lib/types"
import { getSubscription } from "~/models/site.model"
@@ -13,13 +14,11 @@ import { getSubscription } from "~/models/site.model"
type LoaderData =
| {
type: "main"
- appName: string
isLoggedIn: boolean
}
| {
type: "tenant"
isLoggedIn: boolean
- appName: string
tenant?: string
posts: PostOnSiteHome[]
site: SiteLayoutProps["site"]
@@ -62,7 +61,6 @@ export const loader: LoaderFunction = async (ctx) => {
return json({
type: "tenant",
isLoggedIn,
- appName: APP_NAME,
tenant,
posts: pages,
site: {
@@ -75,7 +73,10 @@ export const loader: LoaderFunction = async (ctx) => {
})
}
- return json({ type: "main", isLoggedIn, appName: APP_NAME })
+ return json({
+ type: "main",
+ isLoggedIn,
+ })
}
export default function Home() {
@@ -93,9 +94,5 @@ export default function Home() {
)
}
- return (
-
- {""}
-
- )
+ return {""}
}
diff --git a/server/fly.ts b/server/fly.ts
index f0b47481..976babb7 100644
--- a/server/fly.ts
+++ b/server/fly.ts
@@ -1,13 +1,12 @@
import { type RequestHandler } from "express"
-import {
- FLY_REGION,
- IS_PRIMARY_REGION,
- PRIMARY_REGION,
-} from "~/lib/config.server"
-import { IS_PROD } from "~/lib/config.shared"
+import { IS_PROD } from "~/lib/constants"
+import { getServerEnv, isPrimaryRegion } from "~/lib/env"
+
+const PRIMARY_REGION = getServerEnv("PRIMARY_REGION")
+const FLY_REGION = getServerEnv("FLY_REGION")
export const setFlyRegionHeader: RequestHandler = (req, res, next) => {
- res.setHeader("x-fly-region", FLY_REGION || "unknown")
+ res.setHeader("x-fly-region", PRIMARY_REGION || "unknown")
next()
}
@@ -17,7 +16,7 @@ export const getReplayResponse: RequestHandler = (req, res, next) => {
if (
!IS_PROD ||
["GET", "OPTIONS", "HEAD"].includes(method) ||
- IS_PRIMARY_REGION
+ isPrimaryRegion()
) {
return next()
}
diff --git a/types.d.ts b/types.d.ts
index 9329edbe..cee843e1 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -1,23 +1,30 @@
-declare namespace NodeJS {
- interface ProcessEnv {
- // Additional environment variables
- APP_NAME: string
- DATABASE_URL: string
- AUTH_COOKIE_NAME: string
- AUTH_SECRET: string
- OUR_DOMAIN: string
- S3_REGION: string
- S3_ACCESS_KEY_ID: string
- S3_SECRET_ACCESS_KEY: string
- S3_BUCKET_NAME: string
- S3_ENDPOINT?: string
- MAILGUN_APIKEY: string
- MAILGUN_DOMAIN: string
- MAILGUN_EU?: string
- }
+declare interface ServerEnv {
+ // Additional environment variables
+ APP_NAME: string
+ DATABASE_URL: string
+ AUTH_COOKIE_NAME: string
+ OUR_DOMAIN: string
+ S3_REGION: string
+ S3_ACCESS_KEY_ID: string
+ S3_SECRET_ACCESS_KEY: string
+ S3_BUCKET_NAME: string
+ S3_ENDPOINT?: string
+ MAILGUN_APIKEY: string
+ MAILGUN_DOMAIN: string
+ MAILGUN_EU?: string
+ S3_CDN_PREFIX: string
+ FLY_REGION?: string
+ PRIMARY_REGION?: string
}
-declare const ENV: {
+declare interface BrowserEnv {
APP_NAME: string
OUR_DOMAIN: string
+ S3_CDN_PREFIX: string
}
+
+declare namespace NodeJS {
+ interface ProcessEnv extends ServerEnv {}
+}
+
+declare const ENV: BrowserEnv