mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
www builds currently bypass Turbo. This caches Next.js compilation and sitemap generation while keeping content refreshes and asset uploads on every build, including cache hits. **Changed:** - Refresh content before Turbo hashes its inputs, and upload assets after Turbo saves or restores the build output. - Include generated content, shared code, environment settings, sitemaps, and the customer RSS feed in the cache configuration. - Remove the redundant `vercel.json` build override and consolidate public environment settings into `NEXT_PUBLIC_*`. Cache reuse requires the same commit and build inputs because production CDN URLs include the commit SHA. **Added:** - Build lifecycle tests covering cache restoration, input invalidation, root/app commands, and upload ordering and failure handling. ## To test - From `apps/www`, run `pnpm exec vitest run turbo-build.test.ts generate-sitemap.test.ts scripts/lib/githubStars.test.ts`. - Check the Vercel preview build runs content preparation before `build:next`, and verify the homepage, `/sitemap.xml`, and `/customers-rss.xml` load. - Rebuild with identical prepared content and environment settings to check compilation is cached. Asset uploads should still run when enabled. Validation: 63 tests passed, along with typecheck, ESLint for the new test, formatting, and a full local build. The local build used public example settings and placeholder survey configuration, with asset uploads disabled; Vercel deployment validation is still pending. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Build & Deployment** - Production builds now reuse cached outputs and restore generated assets when a cache is available. - Static assets upload after a successful build, and changes to content, documentation, configuration, or shared components trigger a fresh build. - **Documentation** - Added production build guidance covering caching, CDN uploads, overrides, and direct-build limitations. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
269 lines
12 KiB
Markdown
269 lines
12 KiB
Markdown
# supabase.com
|
|
|
|
## Overview
|
|
|
|
Refer to the [Development Guide](../../DEVELOPERS.md) to learn how to run this site locally.
|
|
|
|
To get started copy the example env file using `cp .env.local.example .env.local`.
|
|
|
|
## Production builds
|
|
|
|
Run `pnpm build` from `apps/www`, or `pnpm build --filter=www` from the repository
|
|
root. Vercel uses the app's `build` script by default.
|
|
|
|
`prebuild` refreshes remote content before Turbo hashes it. Turbo caches
|
|
`build:next` (Next.js, sitemaps, and the customer RSS feed), then `postbuild`
|
|
uploads assets even on cache hits. The outer `build` task stays uncached so
|
|
neither preparation nor upload can be skipped. Uploads run after caching because
|
|
the upload script removes `.next/static`.
|
|
|
|
Production cache reuse requires the same commit and inputs: CDN URLs include
|
|
`VERCEL_GIT_COMMIT_SHA`. Set `FORCE_ASSET_CDN=-1` to disable local uploads.
|
|
Running `build:next` directly skips content preparation and uploads.
|
|
|
|
Run `pnpm exec vitest run turbo-build.test.ts` to check cache invalidation and
|
|
asset restoration using real pnpm, Turbo, and the upload script with fixture
|
|
generators, a stub compiler, and a fake AWS CLI.
|
|
|
|
## Best practices
|
|
|
|
### Images
|
|
|
|
- **Resize images**: All new images should be resized to only the maximum resolution needed when rendering on the frontend. Don't upload images larger than what will be displayed.
|
|
- **Compress images**: All new images should be compressed before committing. Use tools like Clop or ImageOptim to reduce file size without noticeable quality loss.
|
|
- **Image locations**: Store blog post images in `apps/www/public/images/blog/`. Event images go in `apps/www/public/images/events/`. Customer logos go in `apps/www/public/images/customers/logos/on-light/` and `on-dark/` (see Customer Stories below).
|
|
|
|
### OG image generation
|
|
|
|
Open Graph (OG) images for social sharing are handled differently across content types:
|
|
|
|
- **Blog posts**: Use static images via `imgSocial` and `imgThumb` fields (see _Blog posts_ section below)
|
|
- **Events**: Use dynamic generation via Edge Function (with optional `og_image` override)
|
|
- **Customer stories**: Use dynamic generation via Edge Function (no static option)
|
|
|
|
The `og-images` Edge Function (`supabase/functions/og-images/`) automatically generates OG images for events and customer stories. It's deployed via `.github/workflows/og_images.yml` when changes are made to the function code.
|
|
|
|
**Development**: In local development, the function runs at `http://127.0.0.1:54321/functions/v1/og-images`. Ensure Supabase is running locally (`supabase start`).
|
|
|
|
### Content frontmatter image fields
|
|
|
|
Different content types use different image field conventions:
|
|
|
|
#### Blog posts
|
|
|
|
Blog posts support two image fields in their frontmatter:
|
|
|
|
- **`imgSocial`**: Used for Open Graph and social media sharing (X, LinkedIn, etc.). These images should include text overlays since they appear standalone in social feeds without accompanying text.
|
|
- **`imgThumb`**: Used for internal thumbnails displayed on the blog listing pages and featured posts. These images don't need text overlays since they're always displayed alongside the post title and description.
|
|
|
|
This naming convention was introduced to replace the previously confusing `thumb` and `og` fields, which were often mixed up. The new names clearly indicate:
|
|
|
|
- **`imgSocial`**: Purpose-built for social media sharing (needs text overlays)
|
|
- **`imgThumb`**: Optimized for site display (clean, no text overlays)
|
|
|
|
This separation allows you to optimize images for their specific use case while maintaining a clear, unambiguous naming convention.
|
|
|
|
**Image path format**
|
|
|
|
Always use **relative paths** (just the filename or subfolder/filename). The `/images/blog/` prefix is added automatically by the code.
|
|
|
|
- ✅ Correct: `imgSocial: my-post/og.png` or `imgSocial: og.png`
|
|
- ❌ Wrong: `imgSocial: /images/blog/my-post/og.png` (creates double prefix)
|
|
|
|
**Image fallback behavior**
|
|
|
|
For site display (what visitors see):
|
|
|
|
- Priority 1: `imgThumb`
|
|
- Priority 2: `imgSocial` (if `imgThumb` is missing)
|
|
- Priority 3: `/images/blog/blog-placeholder.png` (if both missing)
|
|
|
|
For social sharing (Open Graph meta tags):
|
|
|
|
- Priority 1: `imgSocial`
|
|
- Priority 2: `imgThumb` (if `imgSocial` is missing)
|
|
- Priority 3: No fallback (undefined)
|
|
|
|
What happens if fields are not provided?
|
|
|
|
- If only `imgThumb` is provided: Site displays the image correctly, social sharing uses `imgThumb` as fallback
|
|
- If only `imgSocial` is provided: Social sharing uses it, site display uses it as fallback
|
|
- If neither is provided: Site shows placeholder image, social sharing has no image
|
|
- Best practice: Provide both fields for optimal display and social sharing
|
|
|
|
**Example**
|
|
|
|
```yaml
|
|
---
|
|
title: 'My Blog Post'
|
|
imgSocial: 2025-01-01-my-post/og.png # Relative path - with text overlay for social sharing
|
|
imgThumb: 2025-01-01-my-post/thumb.png # Relative path - without text, clean image
|
|
---
|
|
```
|
|
|
|
The images would be stored at:
|
|
|
|
- `apps/www/public/images/blog/2025-01-01-my-post/og.png`
|
|
- `apps/www/public/images/blog/2025-01-01-my-post/thumb.png`
|
|
|
|
Or if using the same image for both:
|
|
|
|
```yaml
|
|
---
|
|
title: 'My Blog Post'
|
|
imgSocial: my-image.png # Stored at: apps/www/public/images/blog/my-image.png
|
|
imgThumb: my-image.png
|
|
---
|
|
```
|
|
|
|
#### Blog post dates
|
|
|
|
Use quoted `YYYY-MM-DD` values. `date` is the publication date. Set optional `updated` for substantive content revisions, excluding typo, link, or image fixes. It must be on or after `date` and supplies the sitemap and structured-data modification date. When absent, `date` supplies both.
|
|
|
|
#### Events
|
|
|
|
Events use different image fields to avoid confusion with their display patterns:
|
|
|
|
- **`thumb`**: Used for event grid item thumbnails (small cards in listing)
|
|
- **`cover_url`**: Used for the featured event banner (large display on events page)
|
|
- **`og_image`** (optional): Used to override the dynamically generated OG image for social sharing
|
|
|
|
**OG image generation**
|
|
|
|
Events automatically generate Open Graph images using the `og-images` Supabase Edge Function. The function creates images dynamically based on:
|
|
|
|
- Event type (conference, hackathon, etc.)
|
|
- Title (or `meta_title` if provided)
|
|
- Description (or `meta_description` if provided)
|
|
- Date (formatted as "DD MMM YYYY" using the event's timezone)
|
|
- Duration (if provided)
|
|
|
|
If you need a custom OG image that differs from the auto-generated one, you can provide an `og_image` field in the frontmatter. This will override the dynamic generation.
|
|
|
|
**Example:**
|
|
|
|
```yaml
|
|
---
|
|
title: 'Supabase Meetup'
|
|
thumb: /images/events/2025-01-meetup/thumbnail.png
|
|
cover_url: https://external-cdn.com/event-banner.jpg
|
|
og_image: /images/events/2025-01-meetup/custom-og.png # Optional override
|
|
---
|
|
```
|
|
|
|
**Note**: The `og_image` field is optional. If not provided, OG images are generated automatically via the Edge Function.
|
|
|
|
## End-to-end checks
|
|
|
|
Content pages are loaded and scanned with axe-core by the Playwright suite in
|
|
`e2e/www`. Pull requests test the pages your change affects. Today the suite
|
|
enforces one accessibility rule, `page-has-heading-one`.
|
|
|
|
To test the pages your current branch changes:
|
|
|
|
```bash
|
|
PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:www
|
|
```
|
|
|
|
That resolves which pages to test from your branch, but reads them from
|
|
production, so it won't see your edits and will 404 on a page you just added.
|
|
Point `PLAYWRIGHT_BASE_URL` at your pull request's preview to test your own
|
|
content.
|
|
|
|
See [`e2e/www/README.md`](https://github.com/supabase/supabase/blob/master/e2e/www/README.md)
|
|
for coverage and limits.
|
|
|
|
## Go pages (`/go/*`)
|
|
|
|
`/go/` is a system for building standalone campaign landing pages (lead generation, legal, thank-you flows). The name is intentionally generic — these pages are typically linked from ads, emails, or partner campaigns and are not part of the main site navigation.
|
|
|
|
Pages are defined as TypeScript objects (not MDX files) and validated against Zod schemas at build time.
|
|
|
|
### Parts
|
|
|
|
| Location | Purpose |
|
|
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
| `apps/www/_go/` | Page definitions. Each file exports a page object. `index.tsx` registers all pages. |
|
|
| `apps/www/app/go/[slug]/page.tsx` | App Router route — renders the page for a given slug, handles 404s and metadata. |
|
|
| `apps/www/components/Go/GoPageRenderer.tsx` | www-specific wrapper — adds the Supabase logo header and footer, registers custom section renderers. |
|
|
| `packages/marketing/src/go/` | Framework-agnostic core: schemas, section components, templates, form server action. |
|
|
| `packages/marketing/src/crm/` | CRM client abstraction (HubSpot + Customer.io) used by the form server action. |
|
|
|
|
### Page structure
|
|
|
|
Each page specifies a `template` which determines its top-level layout:
|
|
|
|
- **`lead-gen`** — hero + arbitrary sections (form, metrics, feature grid, tweets, social proof, etc.)
|
|
- **`thank-you`** — hero + sections + confetti animation
|
|
- **`legal`** — hero + table-of-contents sidebar + markdown body
|
|
|
|
Pages are arrays of typed section objects. The `SectionRenderer` in `packages/marketing` dispatches each section to the right component based on its `type` field.
|
|
|
|
### Custom renderers
|
|
|
|
The `marketing` package doesn't know about `topTweets` data or the Pages Router `basePath`, so the `tweets` section type has no default renderer. `GoPageRenderer.tsx` in www registers `TweetsSection` as a custom renderer for that type. This is the extension point for any section that requires www-specific dependencies.
|
|
|
|
### Adding a new page
|
|
|
|
1. Create a new file in `apps/www/_go/<category>/my-page.tsx` exporting a page object.
|
|
2. Register it in `apps/www/_go/index.tsx`.
|
|
3. The page will be available at `/go/<slug>` automatically via static generation.
|
|
|
|
#### Customer Stories (Case Studies)
|
|
|
|
Customer stories are defined in MDX files (`apps/www/_customers/*.mdx`) and use a different approach:
|
|
|
|
- **No `og_image` field**: Customer stories do NOT use static OG images
|
|
- **Dynamic OG generation**: All customer story OG images are automatically generated using the `og-images` Supabase Edge Function
|
|
- The function creates images based on the customer `slug` and `title` (or `meta_title` if provided)
|
|
|
|
Do not include an `og_image` field in customer story frontmatter. It will be ignored. OG images are always generated dynamically.
|
|
|
|
**Logos**
|
|
|
|
Customer logos are monochrome and transparent. Prefer SVG; PNG is also fine. Put each theme variant in the folder named for the background it sits on:
|
|
|
|
| Folder | Mark | Frontmatter | Shown in |
|
|
| ----------------------------------- | ----------- | -------------- | ---------- |
|
|
| `/images/customers/logos/on-light/` | Dark/black | `logo` | Light mode |
|
|
| `/images/customers/logos/on-dark/` | Light/white | `logo_inverse` | Dark mode |
|
|
|
|
Do not use coloured brand marks. Preview `/customers` in both themes before merging.
|
|
|
|
Icon-only assets (homepage chips, etc.) stay at `/images/customers/logos/{slug}-icon.svg`.
|
|
|
|
Customer story OG images are generated by the `og-images` Edge Function, which fetches `https://supabase.com/images/customers/logos/on-dark/{slug}.png`. Satori cannot rasterise SVG, so stories still need an `on-dark/{slug}.png` even if the site uses SVG.
|
|
|
|
**Example** (in `apps/www/_customers/company-abc.mdx`):
|
|
|
|
```yaml
|
|
---
|
|
name: Company ABC
|
|
title: Company ABC built their platform with Supabase
|
|
# DO NOT include og_image - it's generated automatically
|
|
logo: /images/customers/logos/on-light/company-abc.svg
|
|
logo_inverse: /images/customers/logos/on-dark/company-abc.svg
|
|
---
|
|
```
|
|
|
|
**Legacy Case Studies** (in `data/CustomerStories.ts`):
|
|
|
|
- **`imgUrl`**: Path to the case study image in the source data
|
|
- This gets mapped to `imgThumb` when rendered via `BlogGridItem` component
|
|
- Case studies only need one image for site display (no separate social sharing image)
|
|
|
|
**Example** (in `data/CustomerStories.ts`):
|
|
|
|
```typescript
|
|
{
|
|
type: 'Customer Story',
|
|
title: 'Company ABC built their platform with Supabase',
|
|
description: '...',
|
|
organization: 'Company ABC',
|
|
imgUrl: 'images/customers/logos/on-light/company-abc.svg', // Full path from public/
|
|
logo: '/images/customers/logos/on-light/company-abc.svg',
|
|
logo_inverse: '/images/customers/logos/on-dark/company-abc.svg',
|
|
url: '/customers/company-abc',
|
|
}
|
|
```
|