Skip to content

Commit 59f97f1

Browse files
committed
feat(web): add PWA support with Serwist and essential app files
- Add @serwist/turbopack for service worker with offline fallback - Add robots.ts, sitemap.ts, manifest.ts for SEO/PWA - Add not-found.tsx, offline/page.tsx for error states - Add /api/health endpoint for Cloud Run health checks - Move security headers to next.config.ts, add CSP - Clean up proxy.ts (remove duplicate security headers)
1 parent e1455d1 commit 59f97f1

13 files changed

Lines changed: 427 additions & 56 deletions

File tree

apps/web/next.config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ const securityHeaders = [
3434
key: "Permissions-Policy",
3535
value: "camera=(), microphone=(), geolocation=()",
3636
},
37+
{
38+
key: "Content-Security-Policy",
39+
value: [
40+
"default-src 'self'",
41+
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
42+
"style-src 'self' 'unsafe-inline'",
43+
"img-src 'self' data: blob: https:",
44+
"font-src 'self' data:",
45+
"connect-src 'self' https:",
46+
"frame-ancestors 'none'",
47+
"base-uri 'self'",
48+
"form-action 'self'",
49+
].join("; "),
50+
},
3751
];
3852

3953
const nextConfig: NextConfig = {

apps/web/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
"@tanstack/react-form-devtools": "^0.2.11",
2626
"@tanstack/react-query": "^5.90.16",
2727
"@tanstack/react-query-devtools": "^5.91.2",
28+
"@serwist/turbopack": "^9.5.0",
29+
"serwist": "^9.5.0",
2830
"jotai": "^2.16.1",
2931
"jotai-location": "^0.6.2",
3032
"zod": "^4.2.1",

apps/web/pnpm-lock.yaml

Lines changed: 282 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function GET() {
2+
return Response.json({ status: "ok" });
3+
}

apps/web/src/app/manifest.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { MetadataRoute } from "next";
2+
3+
export default function manifest(): MetadataRoute.Manifest {
4+
return {
5+
name: "Fullstack Starter",
6+
short_name: "Fullstack",
7+
description: "Modern fullstack monorepo template",
8+
start_url: "/",
9+
display: "standalone",
10+
background_color: "#ffffff",
11+
theme_color: "#000000",
12+
icons: [
13+
{
14+
src: "/favicon.ico",
15+
sizes: "any",
16+
type: "image/x-icon",
17+
},
18+
],
19+
};
20+
}

apps/web/src/app/not-found.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Link from "next/link";
2+
3+
export default function NotFound() {
4+
return (
5+
<div className="flex min-h-screen flex-col items-center justify-center">
6+
<h1 className="text-4xl font-bold">404</h1>
7+
<p className="mt-2 text-gray-600">Page not found</p>
8+
<Link href="/" className="mt-4 text-blue-600 hover:underline">
9+
Go back home
10+
</Link>
11+
</div>
12+
);
13+
}

apps/web/src/app/offline/page.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function OfflinePage() {
2+
return (
3+
<div className="flex min-h-screen flex-col items-center justify-center">
4+
<h1 className="text-2xl font-bold">You're offline</h1>
5+
<p className="mt-2 text-gray-600">Please check your internet connection.</p>
6+
</div>
7+
);
8+
}

apps/web/src/app/providers.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { SerwistProvider } from "@serwist/turbopack/react";
34
import { QueryClientProvider } from "@tanstack/react-query";
45
import { Provider as JotaiProvider } from "jotai";
56
import { type AbstractIntlMessages, NextIntlClientProvider } from "next-intl";
@@ -26,13 +27,15 @@ export function Providers({ children, locale, messages }: ProvidersProps) {
2627
const queryClient = getQueryClient();
2728

2829
return (
29-
<QueryClientProvider client={queryClient}>
30-
<JotaiProvider>
31-
<NextIntlClientProvider locale={locale} messages={messages}>
32-
{children}
33-
</NextIntlClientProvider>
34-
</JotaiProvider>
35-
<TanStackDevTools />
36-
</QueryClientProvider>
30+
<SerwistProvider swUrl="/serwist/sw.js">
31+
<QueryClientProvider client={queryClient}>
32+
<JotaiProvider>
33+
<NextIntlClientProvider locale={locale} messages={messages}>
34+
{children}
35+
</NextIntlClientProvider>
36+
</JotaiProvider>
37+
<TanStackDevTools />
38+
</QueryClientProvider>
39+
</SerwistProvider>
3740
);
3841
}

apps/web/src/app/robots.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { MetadataRoute } from "next";
2+
3+
export default function robots(): MetadataRoute.Robots {
4+
return {
5+
rules: {
6+
userAgent: "*",
7+
allow: "/",
8+
},
9+
sitemap: `${process.env.NEXT_PUBLIC_SITE_URL}/sitemap.xml`,
10+
};
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { createSerwistRoute } from "@serwist/turbopack";
2+
3+
const revision = process.env.NEXT_PUBLIC_GIT_COMMIT ?? crypto.randomUUID();
4+
5+
export const { dynamic, dynamicParams, revalidate, generateStaticParams, GET } = createSerwistRoute(
6+
{
7+
additionalPrecacheEntries: [{ revision, url: "/offline" }],
8+
nextConfig: {},
9+
swSrc: "src/app/sw.ts",
10+
}
11+
);

0 commit comments

Comments
 (0)