mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
I added content dates to `sitemap_www.xml` and `dateModified` to blog
JSON-LD so crawlers can compare freshness with page metadata. Both use
`updated` when present, otherwise the publication date.
**Changed:**
- **Consistent dates:** I use the same frontmatter parser for the blog
page and sitemap. It preserves authored dates across quoting and
timezones and rejects JavaScript frontmatter.
- **Invalid dates stop the build:** I reject impossible calendar days,
out-of-range times and offsets, malformed dates, and `updated` before
publication. Content changes run the generator in CI.
- **Authoring:** I documented optional `updated` for substantive
revisions. Events, static pages, and `/evals` omit `<lastmod>`.
**Note:** Changelog dates come from RSS. Existing sitemap omissions for
nonnumeric changelog slugs (GROWTH-1212) and app-router pages
(GROWTH-1214) remain separate.
## To test
On the preview:
- [x] Open `/sitemap_www.xml`: blog, alternatives, customer stories, and
included changelog entries should carry `YYYY-MM-DD` lastmod values.
Verified on the 2092513 preview: all 425 blog, 3 alternatives, 43
customer story, and 207 changelog entries carry a `YYYY-MM-DD` lastmod,
zero malformed values. Production currently emits no lastmod at all.
- [x] Inspect `/blog/supabase-is-now-available-in-gemini-enterprise`:
BlogPosting `dateModified` should be `2026-09-09`, matching its sitemap
entry. Verified: one BlogPosting block, `datePublished` and
`dateModified` both `2026-09-09`, sitemap lastmod `2026-09-09`.
- [x] Find the `/company` and event entries in the sitemap: neither
should carry lastmod. Verified: `/company` and all 13 `/events/` entries
have no lastmod.
- [x] Added: `/evals` and the `/changelog` index carry no lastmod
either.
- [x] Added: the blog page renders with no new console errors. The only
console error is a `/docs?_rsc=` prefetch 404: the preview host serves
404 for `/docs` itself, unrelated to this change.
## Linear
- fixes GROWTH-1206
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Blog posts now support optional updated dates for substantive
revisions.
- Sitemap entries include accurate modification dates for blog,
alternatives, customers, and changelog content.
- Blog structured data now includes the post’s modification date.
- **Bug Fixes**
- Improved validation prevents invalid or inconsistent content dates
from generating incorrect sitemap data.
- Changelog sitemap links are deduplicated and assigned their published
dates.
- **Documentation**
- Added guidance for specifying publication and update dates in blog
post metadata.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
250 lines
11 KiB
Markdown
250 lines
11 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`.
|
|
|
|
## 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',
|
|
}
|
|
```
|