lynkow 3.6.0 → 3.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +370 -4
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -65,18 +65,63 @@ interface ClientConfig {
65
65
  }
66
66
  ```
67
67
 
68
- ### Next.js App Router Example
68
+ ### Next.js App Router Caching
69
+
70
+ > **Important**: By default, Next.js App Router caches all `fetch()` requests **indefinitely**. This means content updates in Lynkow won't appear until you redeploy or clear the cache.
71
+
72
+ You must configure `fetchOptions` to control caching behavior:
73
+
74
+ #### Option 1: No Cache (Always Fresh)
75
+
76
+ Best for frequently updated content or during development:
77
+
78
+ ```typescript
79
+ const lynkow = createClient({
80
+ siteId: process.env.LYNKOW_SITE_ID!,
81
+ fetchOptions: {
82
+ cache: 'no-store', // Disable Next.js fetch cache entirely
83
+ },
84
+ })
85
+ ```
86
+
87
+ #### Option 2: Time-based Revalidation (ISR)
88
+
89
+ Best for production with occasional updates:
90
+
91
+ ```typescript
92
+ const lynkow = createClient({
93
+ siteId: process.env.LYNKOW_SITE_ID!,
94
+ fetchOptions: {
95
+ next: { revalidate: 60 }, // Revalidate every 60 seconds
96
+ },
97
+ })
98
+ ```
99
+
100
+ #### Option 3: On-Demand Revalidation
101
+
102
+ For precise cache control, use Next.js [on-demand revalidation](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#on-demand-revalidation):
69
103
 
70
104
  ```typescript
105
+ // lib/lynkow.ts
71
106
  const lynkow = createClient({
72
107
  siteId: process.env.LYNKOW_SITE_ID!,
73
- locale: 'fr',
74
108
  fetchOptions: {
75
- next: { revalidate: 60 }, // ISR: revalidate every 60 seconds
109
+ next: { tags: ['lynkow'] }, // Tag all requests
76
110
  },
77
111
  })
112
+
113
+ // app/api/revalidate/route.ts
114
+ import { revalidateTag } from 'next/cache'
115
+
116
+ export async function POST(request: Request) {
117
+ // Verify webhook secret here
118
+ revalidateTag('lynkow')
119
+ return Response.json({ revalidated: true })
120
+ }
78
121
  ```
79
122
 
123
+ Then configure a webhook in Lynkow to call your revalidate endpoint when content changes.
124
+
80
125
  ## Browser vs Server
81
126
 
82
127
  The SDK is isomorphic and works on both browser and server environments. Some features are browser-only:
@@ -522,13 +567,328 @@ console.log(lynkow.locale) // 'fr'
522
567
  // Get available locales
523
568
  console.log(lynkow.availableLocales) // ['fr', 'en', 'es']
524
569
 
525
- // Change locale
570
+ // Change locale (browser-only)
526
571
  lynkow.setLocale('en')
527
572
 
528
573
  // Locale detection (browser-only)
529
574
  // Priority: localStorage > URL path > HTML lang > default
530
575
  ```
531
576
 
577
+ ## Next.js App Router Integration
578
+
579
+ The SDK works seamlessly with Next.js App Router, but there are important patterns to follow for Server Components and internationalization.
580
+
581
+ ### Basic Setup
582
+
583
+ ```typescript
584
+ // lib/lynkow.ts
585
+ import { createClient } from 'lynkow'
586
+
587
+ export const lynkow = createClient({
588
+ siteId: process.env.NEXT_PUBLIC_LYNKOW_SITE_ID!,
589
+ })
590
+ ```
591
+
592
+ ### Server Components
593
+
594
+ In Server Components, you can fetch data directly:
595
+
596
+ ```typescript
597
+ // app/page.tsx
598
+ import { lynkow } from '@/lib/lynkow'
599
+
600
+ export default async function HomePage() {
601
+ const page = await lynkow.pages.getByPath('/')
602
+
603
+ return (
604
+ <main>
605
+ <h1>{page.data.title}</h1>
606
+ </main>
607
+ )
608
+ }
609
+ ```
610
+
611
+ ### Internationalization (i18n)
612
+
613
+ The SDK supports multi-language content via the `locale` option. There are two main approaches:
614
+
615
+ #### Approach 1: Cookie-based Locale (Recommended)
616
+
617
+ This approach uses a cookie to store the user's language preference. The locale is managed entirely via cookie, not URL.
618
+
619
+ **1. Read the cookie in Server Components:**
620
+
621
+ ```typescript
622
+ // app/page.tsx
623
+ import { cookies } from 'next/headers'
624
+ import { lynkow } from '@/lib/lynkow'
625
+
626
+ export default async function HomePage() {
627
+ // Read locale from cookie
628
+ const cookieStore = await cookies()
629
+ const localeCookie = cookieStore.get('_lkw_lang')
630
+
631
+ // Get site config for default locale and i18n settings
632
+ const siteConfig = await lynkow.site.getConfig()
633
+ const locale = localeCookie?.value || siteConfig.defaultLocale || 'fr'
634
+
635
+ // Fetch content with the correct locale
636
+ const page = await lynkow.pages.getByPath('/', { locale })
637
+
638
+ return (
639
+ <main>
640
+ <h1>{page.data.title}</h1>
641
+ <LanguageSwitcher
642
+ i18n={siteConfig.i18n}
643
+ currentLocale={locale}
644
+ />
645
+ </main>
646
+ )
647
+ }
648
+ ```
649
+
650
+ **2. Create a Language Switcher component:**
651
+
652
+ ```typescript
653
+ // components/LanguageSwitcher.tsx
654
+ 'use client'
655
+
656
+ import Cookies from 'js-cookie'
657
+ import type { I18nConfig } from 'lynkow'
658
+
659
+ interface LanguageSwitcherProps {
660
+ i18n: I18nConfig
661
+ currentLocale: string
662
+ }
663
+
664
+ export function LanguageSwitcher({ i18n, currentLocale }: LanguageSwitcherProps) {
665
+ // Don't render if only one locale
666
+ if (!i18n.switcher || i18n.locales.length <= 1) {
667
+ return null
668
+ }
669
+
670
+ const handleLocaleChange = (localeCode: string) => {
671
+ if (localeCode === currentLocale) return
672
+
673
+ // Set cookie with locale preference
674
+ Cookies.set(i18n.detection.cookieName, localeCode, {
675
+ expires: i18n.detection.cookieMaxAge,
676
+ sameSite: 'lax',
677
+ })
678
+
679
+ // Reload page to fetch content in new locale
680
+ window.location.reload()
681
+ }
682
+
683
+ const currentLocaleInfo = i18n.locales.find(l => l.code === currentLocale)
684
+
685
+ return (
686
+ <select
687
+ value={currentLocale}
688
+ onChange={(e) => handleLocaleChange(e.target.value)}
689
+ >
690
+ {i18n.locales.map((locale) => (
691
+ <option key={locale.code} value={locale.code}>
692
+ {locale.flag} {locale.name}
693
+ </option>
694
+ ))}
695
+ </select>
696
+ )
697
+ }
698
+ ```
699
+
700
+ **3. Install js-cookie for client-side cookie management:**
701
+
702
+ ```bash
703
+ npm install js-cookie
704
+ npm install -D @types/js-cookie
705
+ ```
706
+
707
+ #### Approach 2: URL-based Locale (with Next.js i18n)
708
+
709
+ For URL-based routing like `/en/about`, `/fr/about`, use Next.js built-in i18n:
710
+
711
+ ```typescript
712
+ // next.config.js
713
+ module.exports = {
714
+ i18n: {
715
+ locales: ['fr', 'en', 'es'],
716
+ defaultLocale: 'fr',
717
+ },
718
+ }
719
+ ```
720
+
721
+ Then access locale from params:
722
+
723
+ ```typescript
724
+ // app/[locale]/page.tsx
725
+ import { lynkow } from '@/lib/lynkow'
726
+
727
+ interface Props {
728
+ params: { locale: string }
729
+ }
730
+
731
+ export default async function HomePage({ params }: Props) {
732
+ const page = await lynkow.pages.getByPath('/', { locale: params.locale })
733
+
734
+ return <h1>{page.data.title}</h1>
735
+ }
736
+ ```
737
+
738
+ ### Site Configuration & i18n Data
739
+
740
+ The `site.getConfig()` method returns all i18n information needed for language switching:
741
+
742
+ ```typescript
743
+ const config = await lynkow.site.getConfig()
744
+
745
+ // Flat fields for simple usage
746
+ console.log(config.defaultLocale) // 'fr'
747
+ console.log(config.enabledLocales) // ['fr', 'en']
748
+
749
+ // Rich i18n config for building UI
750
+ console.log(config.i18n)
751
+ // {
752
+ // enabled: true,
753
+ // default: 'fr',
754
+ // locales: [
755
+ // { code: 'fr', name: 'Français', flag: '🇫🇷' },
756
+ // { code: 'en', name: 'English', flag: '🇬🇧' }
757
+ // ],
758
+ // switcher: true,
759
+ // detection: {
760
+ // enabled: true,
761
+ // order: ['cookie', 'browser'],
762
+ // cookieName: '_lkw_lang',
763
+ // cookieMaxAge: 365
764
+ // }
765
+ // }
766
+ ```
767
+
768
+ ### Complete Example: Internationalized Page
769
+
770
+ Here's a complete example of a multi-language page with language switcher:
771
+
772
+ ```typescript
773
+ // app/page.tsx
774
+ import { cookies } from 'next/headers'
775
+ import { lynkow } from '@/lib/lynkow'
776
+ import { LanguageSwitcher } from '@/components/LanguageSwitcher'
777
+ import { HomeContent } from './HomeContent'
778
+
779
+ // Default content as fallback
780
+ const defaultContent = {
781
+ title: 'Welcome',
782
+ description: 'Default description',
783
+ }
784
+
785
+ const defaultI18n = {
786
+ enabled: false,
787
+ default: 'en',
788
+ locales: [{ code: 'en', name: 'English', flag: '🇬🇧' }],
789
+ switcher: false,
790
+ detection: {
791
+ enabled: false,
792
+ order: [] as string[],
793
+ cookieName: '_lkw_lang',
794
+ cookieMaxAge: 365
795
+ },
796
+ }
797
+
798
+ export default async function HomePage() {
799
+ let content = defaultContent
800
+ let i18n = defaultI18n
801
+ let currentLocale = 'en'
802
+
803
+ // Read locale preference from cookie
804
+ const cookieStore = await cookies()
805
+ const localeCookie = cookieStore.get('_lkw_lang')
806
+
807
+ try {
808
+ // 1. Get site config (includes i18n settings)
809
+ const siteConfig = await lynkow.site.getConfig()
810
+ if (siteConfig?.i18n) {
811
+ i18n = siteConfig.i18n
812
+ currentLocale = localeCookie?.value || siteConfig.defaultLocale || 'en'
813
+ }
814
+
815
+ // 2. Fetch page content with locale
816
+ const page = await lynkow.pages.getByPath('/', { locale: currentLocale })
817
+ if (page?.data) {
818
+ content = {
819
+ title: page.data.title as string || defaultContent.title,
820
+ description: page.data.description as string || defaultContent.description,
821
+ }
822
+ }
823
+
824
+ // Update locale from actual page response
825
+ if (page?.locale) {
826
+ currentLocale = page.locale
827
+ }
828
+ } catch (error) {
829
+ console.error('Failed to fetch content:', error)
830
+ }
831
+
832
+ return (
833
+ <div>
834
+ <header>
835
+ <LanguageSwitcher i18n={i18n} currentLocale={currentLocale} />
836
+ </header>
837
+ <main>
838
+ <HomeContent content={content} />
839
+ </main>
840
+ </div>
841
+ )
842
+ }
843
+ ```
844
+
845
+ ### Important Notes
846
+
847
+ 1. **Server Components are stateless**: The SDK client doesn't maintain state between requests. Always pass `{ locale }` explicitly to API calls.
848
+
849
+ 2. **Cookie reading requires `cookies()`**: Use `next/headers` to read cookies in Server Components. This automatically makes the route dynamic.
850
+
851
+ 3. **Client-side locale changes**: When the user changes language, set the cookie and reload the page. The server will read the new cookie value.
852
+
853
+ 4. **No automatic locale detection on server**: The SDK's `setLocale()` and locale detection only work in the browser. On the server, you must read the locale from cookies/URL and pass it to API calls.
854
+
855
+ 5. **Page alternates**: Each page response includes `alternates` with links to other language versions:
856
+ ```typescript
857
+ const page = await lynkow.pages.getByPath('/')
858
+ console.log(page.alternates)
859
+ // [
860
+ // { locale: 'fr', path: '/', current: true },
861
+ // { locale: 'en', path: '/', current: false }
862
+ // ]
863
+ ```
864
+
865
+ ### TypeScript Types for i18n
866
+
867
+ ```typescript
868
+ import type { I18nConfig, LocaleInfo, SiteConfig } from 'lynkow'
869
+
870
+ // LocaleInfo
871
+ interface LocaleInfo {
872
+ code: string // 'fr', 'en'
873
+ name: string // 'Français', 'English'
874
+ flag: string // '🇫🇷', '🇬🇧'
875
+ }
876
+
877
+ // I18nConfig
878
+ interface I18nConfig {
879
+ enabled: boolean
880
+ default: string
881
+ locales: LocaleInfo[]
882
+ switcher: boolean
883
+ detection: {
884
+ enabled: boolean
885
+ order: string[]
886
+ cookieName: string
887
+ cookieMaxAge: number
888
+ }
889
+ }
890
+ ```
891
+
532
892
  ## Cache Management
533
893
 
534
894
  The SDK caches API responses automatically.
@@ -652,6 +1012,12 @@ import type {
652
1012
  - **Content enhancements**: Code block copy buttons, text alignment CSS fixes
653
1013
  - Automatic binding with MutationObserver for dynamic content
654
1014
 
1015
+ ### New in v3.6
1016
+
1017
+ - **i18n types**: Added `I18nConfig` and `LocaleInfo` types for building language switchers
1018
+ - **Site config alignment**: API now returns both flat fields (`defaultLocale`, `enabledLocales`) and rich `i18n` object
1019
+ - **Next.js App Router documentation**: Comprehensive guide for Server Components and cookie-based i18n
1020
+
655
1021
  ### Upgrade Steps
656
1022
 
657
1023
  ```typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lynkow",
3
- "version": "3.6.0",
3
+ "version": "3.6.2",
4
4
  "description": "Official SDK for Lynkow Headless",
5
5
  "author": "Lynkow",
6
6
  "license": "SEE LICENSE IN LICENSE",