diff --git a/packages/web/docs/next.config.js b/packages/web/docs/next.config.js index fdaeac5245f..7bce85fafe5 100644 --- a/packages/web/docs/next.config.js +++ b/packages/web/docs/next.config.js @@ -1,6 +1,7 @@ +import withBundleAnalyzer from '@next/bundle-analyzer'; import { withGuildDocs } from '@theguild/components/next.config'; -export default withGuildDocs({ +let config = withGuildDocs({ output: 'export', eslint: { ignoreDuringBuilds: true, @@ -334,3 +335,9 @@ export default withGuildDocs({ return config; }, }); + +if (process.env.ANALYZE === 'true') { + config = withBundleAnalyzer({ enabled: true })(config); +} + +export default config; diff --git a/packages/web/docs/package.json b/packages/web/docs/package.json index 242ce903e0f..5d277048809 100644 --- a/packages/web/docs/package.json +++ b/packages/web/docs/package.json @@ -7,8 +7,10 @@ }, "scripts": { "build": "next build && next-sitemap", + "build:analyze": "ANALYZE=true next build", "dev": "next --turbopack", "postbuild": "pagefind --site .next/server/app --output-path out/_pagefind", + "prettier": "prettier --cache --write --list-different --ignore-unknown src", "validate-mdx-links": "pnpx validate-mdx-links@1.1.0 --files 'src/**/*.mdx'" }, "dependencies": { @@ -32,6 +34,7 @@ }, "devDependencies": { "@mdx-js/typescript-plugin": "^0.0.8", + "@next/bundle-analyzer": "^16.0.0", "@tailwindcss/typography": "0.5.16", "@theguild/tailwind-config": "0.6.3", "@types/react": "18.3.18", diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-request-cancellation/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-request-cancellation/page.mdx index 6834f26f12e..32677c5fa48 100644 --- a/packages/web/docs/src/app/blog/(posts)/graphql-request-cancellation/page.mdx +++ b/packages/web/docs/src/app/blog/(posts)/graphql-request-cancellation/page.mdx @@ -176,7 +176,7 @@ flowchart id31["Load total post likes (Post.likeCount)"] id32["Load comments for each post (Post.comments)"] id4["Load author for each comments (Comment.author)"] - id5["Load author avataer for each comment author (User.avatar)"] + id5["Load author avatar for each comment author (User.avatar)"] id1 --> id21 id1 --> id22 @@ -199,7 +199,7 @@ flowchart id31["Load total post likes (Post.likeCount)"] id32["Load comments for each post (Post.comments)"] id4["Load author for each comments (Comment.author)"] - id5["Load author avataer for each comment author (User.avatar)"] + id5["Load author avatar for each comment author (User.avatar)"] id1 --> id21 id1 --> id22 diff --git a/packages/web/docs/src/app/blog/blog-types.ts b/packages/web/docs/src/app/blog/blog-types.ts index af66fb7d7af..1c24312a221 100644 --- a/packages/web/docs/src/app/blog/blog-types.ts +++ b/packages/web/docs/src/app/blog/blog-types.ts @@ -1,9 +1,11 @@ import type { StaticImageData } from 'next/image'; -import { AuthorId } from '../../authors'; -import { MdxFile, PageMapItem } from '../../mdx-types'; +import type { Author, AuthorId } from '../../authors'; +import type { MdxFile, PageMapItem } from '../../mdx-types'; + +type OneOrMany = T | T[]; export interface BlogFrontmatter { - authors: AuthorId | AuthorId[]; + authors: OneOrMany; title: string; date: string; tags: string | string[]; diff --git a/packages/web/docs/src/app/blog/components/blog-card.tsx b/packages/web/docs/src/app/blog/components/blog-card.tsx index 8ab5822b28f..526855de378 100644 --- a/packages/web/docs/src/app/blog/components/blog-card.tsx +++ b/packages/web/docs/src/app/blog/components/blog-card.tsx @@ -20,11 +20,15 @@ export function BlogCard({ post, className, variant, tag }: BlogCardProps) { const { title, tags } = frontmatter; const date = new Date(frontmatter.date); - const postAuthors: Author[] = ( - typeof frontmatter.authors === 'string' - ? [authors[frontmatter.authors as AuthorId]] - : frontmatter.authors.map(author => authors[author as AuthorId]) - ).filter(Boolean); + const authorsArray = Array.isArray(frontmatter.authors) + ? frontmatter.authors + : [frontmatter.authors]; + + const postAuthors: Author[] = authorsArray + .map((authorId: AuthorId | Author) => + typeof authorId === 'string' ? authors[authorId] : authorId, + ) + .filter(Boolean); if (postAuthors.length === 0) { console.error('author not found', frontmatter); @@ -44,6 +48,7 @@ export function BlogCard({ post, className, variant, tag }: BlogCardProps) { className, )} href={post.route} + scroll >
{firstAuthor.name} @@ -23,3 +29,21 @@ export default async function BlogPage() { ); } + +function coerceCaseStudiesToBlogs(caseStudies: CaseStudyFile[]): BlogPostFile[] { + return caseStudies.map(caseStudy => ({ + ...caseStudy, + frontMatter: { + ...caseStudy.frontMatter, + tags: ['Case Study'], + authors: caseStudy.frontMatter.authors.map( + (author): Author => ({ + name: author.name, + avatar: author.avatar, + link: '' as 'https://', + github: '', + }), + ), + } satisfies BlogFrontmatter, + })); +} diff --git a/packages/web/docs/src/app/case-studies/(posts)/layout.tsx b/packages/web/docs/src/app/case-studies/(posts)/layout.tsx index 73587f812b5..156e03e4938 100644 --- a/packages/web/docs/src/app/case-studies/(posts)/layout.tsx +++ b/packages/web/docs/src/app/case-studies/(posts)/layout.tsx @@ -1,5 +1,4 @@ -import { GetYourAPIGameWhite } from '#components/get-your-api-game-white'; -import { cn, HiveLayoutConfig } from '@theguild/components'; +import { cn, GetYourAPIGameRightSection, HiveLayoutConfig } from '@theguild/components'; import { CaseStudiesHeader } from '../case-studies-header'; import { MoreStoriesSection } from '../more-stories-section'; import '../../hive-prose-styles.css'; @@ -17,10 +16,10 @@ export default function CaseStudiesLayout({ children }: { children: React.ReactN
div:first-of-type>:first-child]:hidden')}> {children} - +
- + ); } diff --git a/packages/web/docs/src/app/case-studies/(posts)/sound-xyz/page.mdx b/packages/web/docs/src/app/case-studies/(posts)/sound-xyz/page.mdx index 1225a9ae7c1..bd6ae286914 100644 --- a/packages/web/docs/src/app/case-studies/(posts)/sound-xyz/page.mdx +++ b/packages/web/docs/src/app/case-studies/(posts)/sound-xyz/page.mdx @@ -4,6 +4,7 @@ excerpt: 'Sound.xyz is revolutionizing the music industry by addressing two critical challenges: the concentration of streaming revenue among top artists and the inadequate compensation per stream.' category: Music +date: 2025-01-27 authors: - name: David Greenstein position: Co-Founder diff --git a/packages/web/docs/src/app/case-studies/(posts)/toast/after-diagram.svg b/packages/web/docs/src/app/case-studies/(posts)/toast/after-diagram.svg new file mode 100644 index 00000000000..3d7897fd03d --- /dev/null +++ b/packages/web/docs/src/app/case-studies/(posts)/toast/after-diagram.svg @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/docs/src/app/case-studies/(posts)/toast/architecture-diagram.svg b/packages/web/docs/src/app/case-studies/(posts)/toast/architecture-diagram.svg new file mode 100644 index 00000000000..6abf59c117d --- /dev/null +++ b/packages/web/docs/src/app/case-studies/(posts)/toast/architecture-diagram.svg @@ -0,0 +1,1018 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/docs/src/app/case-studies/(posts)/toast/before-diagram.svg b/packages/web/docs/src/app/case-studies/(posts)/toast/before-diagram.svg new file mode 100644 index 00000000000..0840ba6af6a --- /dev/null +++ b/packages/web/docs/src/app/case-studies/(posts)/toast/before-diagram.svg @@ -0,0 +1,379 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/docs/src/app/case-studies/(posts)/toast/page.mdx b/packages/web/docs/src/app/case-studies/(posts)/toast/page.mdx new file mode 100644 index 00000000000..15198180aa2 --- /dev/null +++ b/packages/web/docs/src/app/case-studies/(posts)/toast/page.mdx @@ -0,0 +1,383 @@ +export const metadata = { + title: "How Toast's bet on Hive's GraphQL open source stack has paid off at scale", + excerpt: + "Toast's journey of migrating from Apollo to Hive to increased productivity, better support, long-term stability, cost reduction and no vendor lock-in.", + category: 'Point of Sale', + date: '2025-10-23', + authors: [ + { + name: 'Uri Goldshtein', + position: 'The Guild, Founder & CEO', + avatar: new URL('./uri.webp', import.meta.url).href + } + ] +} + +import Image from 'next/image' +import { ComparisonTable as Table } from '@theguild/components' + +import { Lede } from '#components/lede' +import { ScaleBeyondCard } from '#components/scale-beyond-card' + +import afterDiagram from './after-diagram.svg' +import architectureDiagram from './architecture-diagram.svg' +import beforeDiagram from './before-diagram.svg' + +export const Diagram = ({ src }) => ( +
+ +
+) + + + +In this article, we'll cover how Toast, the worldwide market leader in restaurant management software, which powers more than 148,000 restaurants across the US, Canada, the UK, and Ireland, scaled their business to keep moving and shipping fast while growing to an unprecedented scale. + +Scaling to over 100 feature teams, 350 micro-frontends, and 900 people in R&D, they continue to ship +quickly using GraphQL Federation, fully managed by the Hive Platform. + + + +## Summary + +### Overview + +With rapid growth and more than 100 feature teams building in parallel, Toast needed a way to keep +their engineering fast and reliable. + +### Problem + +Apollo’s tooling became a bottleneck. Performance issues, limited support, and vendor lock-in risks +made it harder to manage a large federated setup and slowed down teams. + +### Solution + +Toast adopted Hive’s open GraphQL Federation platform in phases: starting with Hive Console as +schema registry, then introducing GraphQL Yoga and Hive Gateway. This allowed them to modernize +their stack step by step, without disrupting ongoing development. + +### Results + +The move to Hive provided Toast a flexible and reliable foundation for scaling. Federation now +supports 350+ micro-frontends and 80 subgraphs, while teams continue to deliver features quickly and +safely. + + + +## Choosing a schema registry that scales + +At the beginning of adopting federation, Toast compared the different options for a schema registry. +The main options were GraphOS/Apollo Studio and Hive Console. + +After extensive testing, Toast chose Hive Console: + +1. Reliability – Hive Console proved to have significantly better registry uptime than Apollo Studio +2. It had all the features they needed while actively growing and being developed in the open +3. Fair and safe pricing that scales with their needs +4. No vendor-lock which gave them the option to individually choose the best pieces of their + architecture +5. Open source, which made sure it was there for years to come (a bet that proved true) and also + gave them the option to contribute features they cared a lot + ([example](https://github.com/graphql-hive/console/pull/5616)) +6. [Security](https://vercel.com/security) and compliance were paramount for Toast. Hive's + infrastructure came with industry-standard security certifications, such as SOC2 Type2, with a + perfect score, providing peace of mind regarding data protection and privacy. +7. Support – Toast engineers always have direct access and close collaboration with the actual + engineers that build the Hive Platform, through a shared Slack channel – not through intermediary + sales/support engineers + +## Background + +Toast was founded in 2012 with a monolithic Android POS and web application with a single database. +To keep performing efficiently, Toast pivoted to micro-services, allowing teams to move fast +independently. Today they have over 100 feature teams. + +For more details, +[read the full story](https://technology.toasttab.com/entry/navigating-the-graphql-evolution-at-toast-from-bffs-to-federation/) +of how they successfully moved from REST to BFF to GraphQL to GraphQL Federation in order to stay +efficient at huge scale – with a strong focus on productivity for individual teams and clear +separation. + +That article also highlights the importance of a good schema registry to move quickly and safely. + +## Gateway and subgraphs – Starting with Apollo, scaling with Hive + +In the early stages of adopting GraphQL Federation, they used Apollo's gateway and subgraphs. As +they scaled GraphQL Federation across the organization and different teams, they saw some +shortcomings with their federation solution: + +1. The implementations of Apollo Gateway and Apollo Server weren’t well maintained, up to date, or + performant enough +2. Active support from core developers of the platform was lacking, including responses to feature + requests and raised issues + +From a business perspective, these limitations translated into: + +1. More stability issues – outdated components made the system harder to rely on at scale. +2. Higher compute usage – less efficient gateway performance resulted in unnecessary resource + consumption and increased infrastructure costs. +3. Slower issue resolution – lack of active support from core developers delayed fixes and responses + to feature requests, slowing down progress in production. +4. Longer response times – the overhead on Apollo Gateway led to slower app performance, directly + affecting user experience. +5. Lack of clarity about the project's future – uncertainty around its sustainability created a risk + of needing replacement. +6. Possible future issues – security updates and outdated or vulnerable dependencies. + +## Gradual migration path + +Thanks to Hive and The Guild’s open-source tooling, it was possible to gradually migrate the +federated architecture, piece by piece, and only when necessary. + +For most companies, the first step is to migrate the schema registry and GraphQL Platform from +Apollo Studio to Hive Console. The technical transition is a one-line change, and during the +evaluation period, The Guild provides companies with free credits so they can send information to +both platforms and compare, making sure the transition is safe first. As Hive Console supports any +gateway and router, including Apollo Gateway and Apollo Router, and also supports any subgraphs, +including Apollo Server, Toast was able to use Hive Console while keeping their existing +implementations. As Toast already chose Hive Console from the start, they were good to go and could +focus on the later phases of the migration when they actually felt the need. + +The second phase of the transition was to migrate from the unmaintained Apollo Server to GraphQL +Yoga server by The Guild, which +[outperforms Apollo Server in every single metric](https://the-guild.dev/graphql/yoga-server/docs/comparison). + +The third phase was to migrate Apollo Gateway to Hive Gateway, which resulted in easier maintenance, +more flexibility and significantly greater performance and efficiency on Toast’s Gateway. That phase +can also be done gradually and in smaller chunks. + +Hive Gateway also provided Toast with access to all of Apollo's Paid Enterprise features for free, +without the need of plans or vendor lock like GraphQL Subscriptions, Defer and others. + +### GraphOS vs. Hive Features + +Paid enterprise-exclusive features for Apollo users that Hive provides for free: + +- **Unlimited Total Users:** Supports a limitless number of developers and consumers. +- **User Roles and Permissions:** Full, customizable control over user access of any size and any + level. +- **Contracts:** A governance feature allowing you to define filtered, public-facing views of your + private Supergraph. +- **Extensibility:** Enables running custom logic on the Hive Gateway for advanced use cases. +- **Telemetry:** Deep, custom observability data collection. +- **SSO & SAML:** Enterprise-grade Single Sign-On integration. +- **Business Support:** **24x7x365** technical support with top-tier SLAs (Service Level + Agreements). +- **Professional Services & Premium Support Packages:** Access to dedicated The Guild experts for + implementation and mission-critical systems. + +**Features Apollo offers for free – and Hive does too:** + +- **Demand Control (Cost-based Limits):** Protects the graph by limiting expensive operations. +- **Operation-based Limits:** Sets limits on query depth, complexity, etc. +- **Request Authorization & JWT Authentication:** Built-in security and access control features at + the router level. +- **Traffic Shaping:** Features such as deduplication and rate limiting. +- **APQ Caching** (often metered/add-on). +- **Entity Caching** (often metered/add-on). +- **Mesh Compose (compared to Apollo Connectors):** Tools for connecting to legacy REST/other data + sources. + + + +## The technical profile and journey of Toast + +In this section we’ll cover more technical details that might be interesting for any GraphQL +developer to learn and get inspired by, for their own journey. + + + + + Number of teams + over 100 feature teams + + + Number of technical people in the org + Around 900 people in R&D + + + Structure of teams + + In general — domain-oriented horizontal full-stack feature teams with a couple of flat focus + teams + + + + Apps, consumers and clients of the graph + + - 350 micro frontends + - Apollo Client + Codegen + - native mobile + - point for improvement — Currently they are not utilizing fragment based co-location too + much. + - next phase — AI consumers of the graph + + + + Number of subgraphs + 80 subgraphs + + + Gateway + + - 2 gateways — one client-facing and one for restaurant admin — both using Hive Gateway + - using Node 20 + + + + Schema Registry + [Hive Console](https://the-guild.dev/graphql/hive) + + + Subgraphs stack + Kotlin with graphql-java - using GraphQL for all new functionality + + + Infrastructure + AWS + + + Auth implementation + + + + Observability and tracing + + - Hive Insights and DataDog tracking + - looking into the new Hive Metrics features + + + + Schema evolution process + + + + Local development process and tooling + + Toast developed their own tool for efficient local development called "PrepStation". The tool deeply integrates with Hive Console, Storybook, GraphQL Codegen and Yoga Server to seamlessly merge local and remote subgraphs and compose them locally. + + It then takes the composed Supergraph and creates a mocked local gateway instance. + + Using this setup, feature teams can prototype and work locally with upcoming versions of the graph, and create full prototypes to stakeholders before production or backend teams need to do any work. The Toast team gave a [great talk](https://graphql.org/conf/2024/schedule/19cf965c68cfae3c7c19c6a9966bcadf/) about it at GraphQL Conf. + + + + +
+ +## How they got to this architecture + +### Initial Approach + +Toast had a long evolution to get to where they are today. Some of it was already discussed at the +beginning of the article, but to expand a bit, they started with one big REST BFF, which grew into +30-40 REST BFFs. They first introduced GraphQL BFF on the guest side. It began with a SPA that had a +Node BFF written in GraphQL. Later, the restaurant admin side also discovered GraphQL, used the same +structure and added 30–40 GraphQL BFFs — each team had their own REST API (Kotlin) with a Node +GraphQL BFF on top. + + + +As more teams, like the Mobile app, needed the same data from multiple BFFs, that led them into +adopting Federation and the federated graph, still using the same model where each team feature had +their own REST API (Kotlin) and Node GraphQL BFF on top. Federation proved to be a great bet, +growing rapidly into the company. Not only on the restaurant admin side, it was also extended to +Guest side, all of them were using GraphQL Federation as their frontend API gateway, which helped +them to bring consistency within all their platforms, including ToastNow, their native mobile +application – a high throughput, simpler auth solution. Over the years, Toast made acquisitions +which merged and federated together into the Supergraph. But as Federated GraphQL BFFs grew by the +numbers, that also led to logic creeping into the BFF layer. + +### Current Architecture + +The next evolution was for the Kotlin side of the team to gradually remove REST, replace it with +GraphQL and directly federate into Hive Gateway, without the need of a Node GraphQL BFF in between. +This is the current architecture but as with all things, it is happening gradually, so there are +still some Node BFFs and when new functionality is added, the Kotlin teams gradually remove REST, +move business logic from the Node BFF (that accumulated there for years) and expose it all as +GraphQL. + +Today, the Hive Gateways federate a combination of Node GraphQL BFFs and Kotlin GraphQL subgraphs +which are all registered into Hive Console. + + + +## Next steps for Toast and the Hive Platform team + +As the Toast GraphQL teams and The Guild are working closely together, we make sure to align our +roadmap with Toast's needs. Here are some of their future plans. + +### Feedback on the new public API + +Since Hive released their new public API, a lot of integrations with internal systems at Toast could +be made much simpler. We are looking forward to seeing how they utilize the new API and if there are +ways we can improve it to make it easier for them. + +One area where we know we want tighter integration is with their internal Backstage system. + +### Usage reporting (OTEL, insights page, and errors) + +We recently introduced a new system for gathering and managing OTEL metrics on Hive for selected +customers. This also affects Hive's current insights page, which uses the current agent and query +schema coordinates to show valuable data for customers. + +As we design these new pages, we work closely with Toast to make sure we take their needs into +account in the new design. + +### New and improved alerting and webhooks system + +Based on the above features, we are currently in the process of redesigning our alerting and +webhooks system. There are a lot of options here, so we work closely with Toast to make sure we are +covering everything they need in the best way possible. + +### Schema proposals + +Hive is close to shipping the first version of schema proposals to selected customers for feedback. +Toast is excited to try it out and see how it will best fit and improve their current schema change +processes. + +### Progressive override + +An important Federation feature that allows gradual migration of features from one subgraph to +another. As Toast is migrating types and fields from the current Node GraphQL BFFs to the Kotlin +subgraphs, this becomes important for them. + +### New laboratory rebuild + +Hive is in the process of completely rebuilding our laboratory experience. We've hired a few people +who built that experience on other platforms and are now bringing that knowledge into a new, +improved experience. + +Toast gave us tons of valuable feedback and we hope to get an exciting new experience to them very +soon. + +### New Hive Rust Router + +The Guild is working hard on +[Hive Router](https://the-guild.dev/graphql/hive/blog/welcome-hive-router), their new GraphQL +Federation Rust Router. + +It is already the [most correct](https://the-guild.dev/graphql/hive/federation-gateway-audit) and +the [most performant](https://the-guild.dev/graphql/hive/federation-gateway-performance) router in +the ecosystem. + +Toast is interested in trying it out, waiting the flexibility of Hive Gateway JS. + +An initial step would be to try the next upcoming version of Hive Gateway, which will include Hive +Router’s Rust query planner and has great performance improvements while keeping the gateway in +javascript still. + +### MCP and AI integrations and use cases + +Toast is very forward-looking and already has many AI use cases, both internal and customer-facing. + +We work hard to make sure they get everything they need for these use cases from their API layer. +That includes reviewing existing solutions, finding the missing points and supporting those in our +platform. + +## Conclusion + +Toast's experience highlights the value of open, flexible GraphQL tooling for companies scaling +fast. Hive enabled them to modernize gradually, improve performance, and reduce costs while keeping +development efficient. Looking ahead, both teams continue working closely to support new needs and +ensure sustainable growth. diff --git a/packages/web/docs/src/app/case-studies/(posts)/toast/uri.webp b/packages/web/docs/src/app/case-studies/(posts)/toast/uri.webp new file mode 100644 index 00000000000..b0f45ee49fd Binary files /dev/null and b/packages/web/docs/src/app/case-studies/(posts)/toast/uri.webp differ diff --git a/packages/web/docs/src/app/case-studies/(posts)/wealthsimple/page.mdx b/packages/web/docs/src/app/case-studies/(posts)/wealthsimple/page.mdx index 495a87afef6..f5ee2a2ab92 100644 --- a/packages/web/docs/src/app/case-studies/(posts)/wealthsimple/page.mdx +++ b/packages/web/docs/src/app/case-studies/(posts)/wealthsimple/page.mdx @@ -4,6 +4,7 @@ excerpt: 'As the company scaled, the need for a robust, flexible, and efficient API architecture became paramount, leading to the adoption of GraphQL with Hive as their central API management solution.' category: Finance +date: 2025-01-27 # We would need to go through approval to get author images. I guess we can leave this as is for now. authors: - name: diff --git a/packages/web/docs/src/app/case-studies/all-case-studies-list.tsx b/packages/web/docs/src/app/case-studies/all-case-studies-list.tsx index f3abb9704df..014db2f1340 100644 --- a/packages/web/docs/src/app/case-studies/all-case-studies-list.tsx +++ b/packages/web/docs/src/app/case-studies/all-case-studies-list.tsx @@ -9,15 +9,16 @@ export function AllCaseStudiesList({ caseStudies }: { caseStudies: CaseStudyFile Explore customer stories -
    +
      {caseStudies.map(caseStudy => { return ( -
    • +
    • ); diff --git a/packages/web/docs/src/app/case-studies/case-studies-header.tsx b/packages/web/docs/src/app/case-studies/case-studies-header.tsx index c82ca542182..58b9b7fb92b 100644 --- a/packages/web/docs/src/app/case-studies/case-studies-header.tsx +++ b/packages/web/docs/src/app/case-studies/case-studies-header.tsx @@ -24,7 +24,7 @@ export function CaseStudiesHeader(props: React.HTMLAttributes) { {frontmatter.title} - + {logo} diff --git a/packages/web/docs/src/app/case-studies/case-study-types.ts b/packages/web/docs/src/app/case-studies/case-study-types.ts index 3b9bf7fccbf..653e761a3fc 100644 --- a/packages/web/docs/src/app/case-studies/case-study-types.ts +++ b/packages/web/docs/src/app/case-studies/case-study-types.ts @@ -5,6 +5,10 @@ export type CaseStudyFrontmatter = { excerpt: string; category: string; authors: CaseStudyAuthor[]; + /** + * YYYY-MM-DD + */ + date: `${number}-${number}-${number}`; }; export type CaseStudyAuthor = { diff --git a/packages/web/docs/src/app/case-studies/company-logos.tsx b/packages/web/docs/src/app/case-studies/company-logos.tsx index 0cf98470dd2..6c56a4e7a21 100644 --- a/packages/web/docs/src/app/case-studies/company-logos.tsx +++ b/packages/web/docs/src/app/case-studies/company-logos.tsx @@ -1,4 +1,4 @@ -import { SoundYXZLogo, WealthsimpleLogo } from '../../components/company-logos'; +import { SoundYXZLogo, ToastLogo, WealthsimpleLogo } from '../../components/company-logos'; /** * Take note that these logos may have different dimensions than logos used elsewhere. @@ -6,6 +6,7 @@ import { SoundYXZLogo, WealthsimpleLogo } from '../../components/company-logos'; export const companyLogos = { 'sound-xyz': , wealthsimple: , + toast: , }; export function getCompanyLogo(company: string) { diff --git a/packages/web/docs/src/app/case-studies/get-case-studies.ts b/packages/web/docs/src/app/case-studies/get-case-studies.ts new file mode 100644 index 00000000000..47d13a790fc --- /dev/null +++ b/packages/web/docs/src/app/case-studies/get-case-studies.ts @@ -0,0 +1,14 @@ +import { getPageMap } from '@theguild/components/server'; +import { isCaseStudy } from './isCaseStudyFile'; + +export async function getCaseStudies() { + const [_meta, _indexPage, ...pageMap] = await getPageMap('/case-studies'); + + const caseStudies = pageMap.filter(isCaseStudy).sort((a, b) => { + const aDate = a.frontMatter.date; + const bDate = b.frontMatter.date; + return aDate < bDate ? 1 : aDate > bDate ? -1 : 0; + }); + + return caseStudies; +} diff --git a/packages/web/docs/src/app/case-studies/looking-to-use-hive-upsell-block.tsx b/packages/web/docs/src/app/case-studies/looking-to-use-hive-upsell-block.tsx index fa7258d6cbd..7f741cc812c 100644 --- a/packages/web/docs/src/app/case-studies/looking-to-use-hive-upsell-block.tsx +++ b/packages/web/docs/src/app/case-studies/looking-to-use-hive-upsell-block.tsx @@ -2,9 +2,9 @@ import { cn, ContactButton, DecorationIsolation, Heading } from '@theguild/compo export function LookingToUseHiveUpsellBlock({ className }: { className?: string }) { return ( -
      @@ -21,7 +21,7 @@ export function LookingToUseHiveUpsellBlock({ className }: { className?: string > Talk to us -
      + ); } diff --git a/packages/web/docs/src/app/case-studies/more-stories-section/index.tsx b/packages/web/docs/src/app/case-studies/more-stories-section/index.tsx index 7a4eb3c77ac..e83424b01a4 100644 --- a/packages/web/docs/src/app/case-studies/more-stories-section/index.tsx +++ b/packages/web/docs/src/app/case-studies/more-stories-section/index.tsx @@ -10,15 +10,7 @@ export async function MoreStoriesSection({ ...rest }: React.HTMLAttributes) { const [_meta, _indexPage, ...pageMap] = await getPageMap('/case-studies'); - let caseStudies = pageMap.filter(isCaseStudy).slice(0, 4); - - if (caseStudies.length < 4) { - if (process.env.NODE_ENV === 'development') { - caseStudies = [...caseStudies, ...caseStudies, ...caseStudies]; - } else { - return null; - } - } + const caseStudies = pageMap.filter(isCaseStudy).slice(0, 4); return (
      diff --git a/packages/web/docs/src/app/case-studies/more-stories-section/other-case-studies.tsx b/packages/web/docs/src/app/case-studies/more-stories-section/other-case-studies.tsx index c2e9a078c4c..09bc049c124 100644 --- a/packages/web/docs/src/app/case-studies/more-stories-section/other-case-studies.tsx +++ b/packages/web/docs/src/app/case-studies/more-stories-section/other-case-studies.tsx @@ -14,12 +14,13 @@ export function OtherCaseStudies({ caseStudies }: { caseStudies: CaseStudyFile[] .slice(0, 3) .map((item, i) => { return ( -
    • +
    • ); diff --git a/packages/web/docs/src/app/case-studies/page.tsx b/packages/web/docs/src/app/case-studies/page.tsx index 569feb54dcb..0d7bc551739 100644 --- a/packages/web/docs/src/app/case-studies/page.tsx +++ b/packages/web/docs/src/app/case-studies/page.tsx @@ -5,7 +5,6 @@ import { Heading, HiveLayoutConfig, } from '@theguild/components'; -import { getPageMap } from '@theguild/components/server'; import { GetYourAPIGameWhite } from '../../components/get-your-api-game-white'; import { HeroLinks } from '../../components/hero'; import { LandingPageContainer } from '../../components/landing-page-container'; @@ -13,16 +12,14 @@ import { TrustedBySection } from '../../components/trusted-by-section'; import { AllCaseStudiesList } from './all-case-studies-list'; import { CaseStudiesArchDecoration, CaseStudiesGradientDefs } from './case-studies-arch-decoration'; import { FeaturedCaseStudiesGrid } from './featured-case-studies-grid'; -import { isCaseStudy } from './isCaseStudyFile'; +import { getCaseStudies } from './get-case-studies'; export const metadata = { title: 'Case Studies', }; export default async function CaseStudiesPage() { - const [_meta, _indexPage, ...pageMap] = await getPageMap('/case-studies'); - - const caseStudies = pageMap.filter(isCaseStudy); + const caseStudies = await getCaseStudies(); return ( diff --git a/packages/web/docs/src/app/global.css b/packages/web/docs/src/app/global.css new file mode 100644 index 00000000000..5cfd68ca146 --- /dev/null +++ b/packages/web/docs/src/app/global.css @@ -0,0 +1,16 @@ +.nextra-sidebar.nextra-sidebar { + width: 300px; + + & ul { + padding-left: 4px; + margin-left: 9px; + + & .x\:text-gray-500 { + @apply text-gray-700 dark:text-neutral-300; + } + } +} + +article.x\:min-h-\[calc\(100vh-var\(--nextra-navbar-height\)\)\] { + @apply md:pl-6 md:pr-8; +} diff --git a/packages/web/docs/src/app/hive-prose-styles.css b/packages/web/docs/src/app/hive-prose-styles.css index 7c0ee590e68..3383ec00d58 100644 --- a/packages/web/docs/src/app/hive-prose-styles.css +++ b/packages/web/docs/src/app/hive-prose-styles.css @@ -4,11 +4,14 @@ & > .main-content { box-sizing: content-box; - width: var(--nextra-content-width); - max-width: 100%; + max-width: min(100%, var(--nextra-content-width)); & > div { - @apply ml-0 pl-6 max-sm:pr-6 md:pl-12; + @apply ml-0 w-full !max-w-full pl-6 max-sm:pr-6 md:pl-12; + } + + & .case-studies-breakout { + @apply max-sm:-mx-6; } & > div > article { @@ -25,6 +28,10 @@ @apply text-green-1000 dark:text-white; + & article { + @apply text-green-1000 dark:text-white; + } + & article main > :is(h2, h3, h4, h5, h6, p, li) { @apply text-green-1000 dark:text-white; } diff --git a/packages/web/docs/src/app/layout.tsx b/packages/web/docs/src/app/layout.tsx index 8c9e079fced..fb64100bfc8 100644 --- a/packages/web/docs/src/app/layout.tsx +++ b/packages/web/docs/src/app/layout.tsx @@ -20,6 +20,7 @@ import '../selection-styles.css'; import '../easing-functions.css'; import '../mermaid.css'; import { NarrowPages } from './narrow-pages'; +import './global.css'; export const metadata = getDefaultMetadata({ productName: PRODUCTS.HIVE.name, diff --git a/packages/web/docs/src/app/product-updates/(posts)/product-update-header.tsx b/packages/web/docs/src/app/product-updates/(posts)/product-update-header.tsx index a7dbca31023..441460d4de3 100644 --- a/packages/web/docs/src/app/product-updates/(posts)/product-update-header.tsx +++ b/packages/web/docs/src/app/product-updates/(posts)/product-update-header.tsx @@ -2,11 +2,11 @@ import { format } from 'date-fns'; import { Anchor, cn, useConfig } from '@theguild/components'; -import { AuthorId, authors } from '../../../authors'; +import { Author, AuthorId, authors } from '../../../authors'; import { SocialAvatar } from '../../../components/social-avatar'; type Meta = { - authors: AuthorId[]; + authors: (AuthorId | Author)[]; date: string; title: string; description: string; @@ -21,8 +21,13 @@ export const ProductUpdateAuthors = ({ }) => { const date = meta.date ? new Date(meta.date) : new Date(); - if (meta.authors.length === 1) { - const author = authors[meta.authors[0] as AuthorId]; + const metaAuthors = meta.authors.map(author => { + return typeof author === 'string' ? authors[author as AuthorId] : author; + }); + + if (metaAuthors.length === 1) { + const author = metaAuthors[0]; + if (!author) { throw new Error(`Author ${meta.authors[0]} not found`); } @@ -66,14 +71,9 @@ export const ProductUpdateAuthors = ({ {format(date, 'EEEE, LLL do y')}
      - {meta.authors.map(authorId => { - const author = authors[authorId as AuthorId]; - if (!author) { - throw new Error(`Author ${authorId} not found`); - } - + {metaAuthors.map(author => { return ( -
      +
      ); } + +export function ToastLogo(props: LogoProps) { + return ( + + ); +} diff --git a/packages/web/docs/src/components/scale-beyond-card.tsx b/packages/web/docs/src/components/scale-beyond-card.tsx new file mode 100644 index 00000000000..fb29bbdeeb9 --- /dev/null +++ b/packages/web/docs/src/components/scale-beyond-card.tsx @@ -0,0 +1,52 @@ +import clsx from 'clsx'; +import { ContactButton, DecorationIsolation, Heading } from '@theguild/components'; + +export function ScaleBeyondCard(props: React.HTMLAttributes) { + return ( +
      + + + Scale beyond Apollo + +
      +

      + Share your stack and we’ll chart your gradual migration path so you keep shipping while + you scale. +

      + Let's talk +
      +
      + ); +} + +function CardDecoration() { + return ( + + + + + + + + + + + + ); +} diff --git a/packages/web/docs/src/mermaid.css b/packages/web/docs/src/mermaid.css index 35c8db9369e..ff70fd0fc94 100644 --- a/packages/web/docs/src/mermaid.css +++ b/packages/web/docs/src/mermaid.css @@ -10,3 +10,15 @@ } } } + +.flowchart { + & .edgeLabel p { + background-color: rgb(var(--nextra-bg)) !important; + } + & .cluster rect { + @apply dark:!fill-beige-800/5 !stroke-beige-300 dark:!stroke-beige-700/25 !fill-blue-300; + } + & .node rect { + @apply dark:!fill-beige-700/10 dark:!stroke-beige-700/25 !fill-blue-100 !stroke-transparent; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b90987eea5b..d369a2e0f5a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -220,7 +220,7 @@ importers: version: 5.7.3 vite-tsconfig-paths: specifier: 5.1.4 - version: 5.1.4(typescript@5.7.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.3.3)(less@4.2.0)(lightningcss@1.28.2)(terser@5.37.0)(tsx@4.19.2)(yaml@2.5.0)) + version: 5.1.4(typescript@5.7.3)(vite@6.3.5(@types/node@22.10.5)(jiti@2.3.3)(less@4.2.0)(lightningcss@1.28.2)(terser@5.37.0)(tsx@4.19.2)(yaml@2.5.0)) vitest: specifier: 3.2.4 version: 3.2.4(@types/node@22.10.5)(jiti@2.3.3)(less@4.2.0)(lightningcss@1.28.2)(terser@5.37.0)(tsx@4.19.2)(yaml@2.5.0) @@ -1427,7 +1427,7 @@ importers: devDependencies: '@graphql-inspector/core': specifier: 5.1.0-alpha-20231208113249-34700c8a - version: 5.1.0-alpha-20231208113249-34700c8a(graphql@16.11.0) + version: 5.1.0-alpha-20231208113249-34700c8a(graphql@16.9.0) '@hive/service-common': specifier: workspace:* version: link:../service-common @@ -2143,6 +2143,9 @@ importers: '@mdx-js/typescript-plugin': specifier: ^0.0.8 version: 0.0.8 + '@next/bundle-analyzer': + specifier: ^16.0.0 + version: 16.0.0 '@tailwindcss/typography': specifier: 0.5.16 version: 0.5.16(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.10.5)(typescript@5.7.3))) @@ -3666,7 +3669,6 @@ packages: '@fastify/vite@6.0.7': resolution: {integrity: sha512-+dRo9KUkvmbqdmBskG02SwigWl06Mwkw8SBDK1zTNH6vd4DyXbRvI7RmJEmBkLouSU81KTzy1+OzwHSffqSD6w==} - bundledDependencies: [] '@floating-ui/core@1.2.6': resolution: {integrity: sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==} @@ -5062,6 +5064,9 @@ packages: '@next/bundle-analyzer@15.1.5': resolution: {integrity: sha512-pCYMPgGRwf+FjEwUXFo3QF14VzBSPPsBHSFuXUpq5ifKcY8LbcmoF2xMVVMa2HoYgA1XuqPSAIfLJr4YXNa9xQ==} + '@next/bundle-analyzer@16.0.0': + resolution: {integrity: sha512-OYufQoNm/Im2fYQBdXu9fqUrXaP3lPuPnryW0XNGG7kJiGxH/VWS8zc2/x4aW2LQGn+opqIJYNRXy7k5qV/09g==} + '@next/env@13.5.6': resolution: {integrity: sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==} @@ -17607,8 +17612,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.596.0 - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -17715,11 +17720,11 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.596.0': + '@aws-sdk/client-sso-oidc@3.596.0(@aws-sdk/client-sts@3.596.0)': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -17758,6 +17763,7 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 transitivePeerDependencies: + - '@aws-sdk/client-sts' - aws-crt '@aws-sdk/client-sso-oidc@3.723.0(@aws-sdk/client-sts@3.723.0)': @@ -17891,11 +17897,11 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sts@3.596.0(@aws-sdk/client-sso-oidc@3.596.0)': + '@aws-sdk/client-sts@3.596.0': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.596.0 + '@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0) '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -17934,7 +17940,6 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/client-sts@3.723.0': @@ -18048,7 +18053,7 @@ snapshots: '@aws-sdk/credential-provider-ini@3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0)': dependencies: - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/credential-provider-env': 3.587.0 '@aws-sdk/credential-provider-http': 3.596.0 '@aws-sdk/credential-provider-process': 3.587.0 @@ -18167,7 +18172,7 @@ snapshots: '@aws-sdk/credential-provider-web-identity@3.587.0(@aws-sdk/client-sts@3.596.0)': dependencies: - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.1.11 '@smithy/types': 3.7.2 @@ -18342,7 +18347,7 @@ snapshots: '@aws-sdk/token-providers@3.587.0(@aws-sdk/client-sso-oidc@3.596.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.596.0 + '@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0) '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.1.11 '@smithy/shared-ini-file-loader': 3.1.12 @@ -19974,13 +19979,6 @@ snapshots: object-inspect: 1.12.3 tslib: 2.6.2 - '@graphql-inspector/core@5.1.0-alpha-20231208113249-34700c8a(graphql@16.11.0)': - dependencies: - dependency-graph: 0.11.0 - graphql: 16.11.0 - object-inspect: 1.12.3 - tslib: 2.6.2 - '@graphql-inspector/core@5.1.0-alpha-20231208113249-34700c8a(graphql@16.9.0)': dependencies: dependency-graph: 0.11.0 @@ -21554,6 +21552,13 @@ snapshots: - bufferutil - utf-8-validate + '@next/bundle-analyzer@16.0.0': + dependencies: + webpack-bundle-analyzer: 4.10.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@next/env@13.5.6': {} '@next/env@15.5.3': {} @@ -36115,6 +36120,17 @@ snapshots: - tsx - yaml + vite-tsconfig-paths@5.1.4(typescript@5.7.3)(vite@6.3.5(@types/node@22.10.5)(jiti@2.3.3)(less@4.2.0)(lightningcss@1.28.2)(terser@5.37.0)(tsx@4.19.2)(yaml@2.5.0)): + dependencies: + debug: 4.3.7(supports-color@8.1.1) + globrex: 0.1.2 + tsconfck: 3.0.3(typescript@5.7.3) + optionalDependencies: + vite: 6.3.5(@types/node@22.10.5)(jiti@2.3.3)(less@4.2.0)(lightningcss@1.28.2)(terser@5.37.0)(tsx@4.19.2)(yaml@2.5.0) + transitivePeerDependencies: + - supports-color + - typescript + vite-tsconfig-paths@5.1.4(typescript@5.7.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.3.3)(less@4.2.0)(lightningcss@1.28.2)(terser@5.37.0)(tsx@4.19.2)(yaml@2.5.0)): dependencies: debug: 4.3.7(supports-color@8.1.1)