Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions config/datocms/lib/createPreviewToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Client } from '@datocms/cli/lib/cma-client-node';

export const createPreviewToken = async (client: Client) => {
const roles = await client.roles.list();
const editorRole = roles.find((role) => role.name === 'Editor');

return client.accessTokens.create({
name: 'Preview',
can_access_cda: true,
can_access_cda_preview: false,
can_access_cma: true,
role: {
type: 'role',
id: editorRole!.id,
},
});
};
66 changes: 66 additions & 0 deletions config/datocms/migrations/1734806654_previewLinks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { Client } from '@datocms/cli/lib/cma-client-node';
import { createPreviewToken } from '../lib/createPreviewToken';

export default async function (client: Client) {
console.log('Manage upload filters');

console.log('Install plugin "Model Deployment Links"');
const previewApiToken = await createPreviewToken(client);
await client.plugins.create({
id: 'MKba9NT5QBKZaeI4HcERwA',
package_name: 'datocms-plugin-model-deployment-links',
});
await client.plugins.update('MKba9NT5QBKZaeI4HcERwA', {
parameters: { datoApiToken: previewApiToken.token },
});

console.log('Creating new fields/fieldsets');

console.log(
'Create JSON field "Preview" (`preview`) in model "\uD83D\uDCD1 Page" (`page`)'
);
await client.fields.create('LjXdkuCdQxCFT4hv8_ayew', {
id: 'XF2LuFVWSrmu7Lle8xlhTg',
label: 'Preview',
field_type: 'json',
api_key: 'preview',
localized: true,
appearance: {
addons: [],
editor: 'MKba9NT5QBKZaeI4HcERwA',
parameters: { urlPattern: '/{ locale }/{ slug }/' },
},
});

console.log(
'Create JSON field "Preview" (`preview`) in model "\uD83C\uDFE0 Home" (`home_page`)'
);
await client.fields.create('X_tZn3TxQY28ltSyjZUGHQ', {
id: 'YPXZOMoWRdKTHLUkN9ytfw',
label: 'Preview',
field_type: 'json',
api_key: 'preview',
localized: true,
appearance: {
addons: [],
editor: 'MKba9NT5QBKZaeI4HcERwA',
parameters: { urlPattern: '/{ locale }/' },
},
});

console.log(
'Create JSON field "Preview" (`preview`) in model "\uD83E\uDD37 Not found" (`not_found_page`)'
);
await client.fields.create('d_AvMVoMSqmNbMqx-NdqIw', {
id: 'O_DCfpaDSzq1MX4DaRlMpQ',
label: 'Preview',
field_type: 'json',
api_key: 'preview',
localized: true,
appearance: {
addons: [],
editor: 'MKba9NT5QBKZaeI4HcERwA',
parameters: { urlPattern: '/{ locale }/404' },
},
});
}
2 changes: 1 addition & 1 deletion datocms-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
* @see docs/getting-started.md on how to use this file
* @see docs/decision-log/2023-10-24-datocms-env-file.md on why file is preferred over env vars
*/
export const datocmsEnvironment = 'action-block';
export const datocmsEnvironment = 'preview-links';
export const datocmsBuildTriggerId = '30535';
12 changes: 12 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ You can now run your project locally:
npm run dev
```

### Configure DatoCMS plugins

Head Start comes with a few DatoCMS plugins pre-installed. Some plugins require additional project-specific configuration:

#### Model Deployments Links plugin

The [Model Deployment Links plugin](https://www.datocms.com/marketplace/plugins/i/datocms-plugin-model-deployment-links) requires an API key:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this could also be achieved within a migration script using https://www.datocms.com/docs/content-management-api/resources/access-token/create . However it's difficult to reliably get the Editor role ID. Since this is a one time config step, I'm happy to have it as a manual step, rather than scripting the generation of access token.


- In your DatoCMS instance go to Project Settings > API Tokens (`/project_settings/access_tokens`) and "Add a new access token". Name it "Preview" (or whatever you prefer), for the "Role associated with this API token
" select "Editor" and keep the other settings as is.
- Go to Enviroment Configuration > Plugins > Model Deployment Links and enter the newly created access token in the plugin settings under "DatoCMS API Token".

Comment on lines +79 to +80
Copy link

Copilot AI Apr 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word 'Enviroment' appears to be misspelled. Consider correcting it to 'Environment'.

Suggested change
- Go to Enviroment Configuration > Plugins > Model Deployment Links and enter the newly created access token in the plugin settings under "DatoCMS API Token".
- Go to Environment Configuration > Plugins > Model Deployment Links and enter the newly created access token in the plugin settings under "DatoCMS API Token".

Copilot uses AI. Check for mistakes.

### Add DatoCMS secrets to repository

Head Start provides GitHub Actions which include linting code and validating HTML on PR changes. These Actions require the DatoCMS tokens to be available.
Expand Down
11 changes: 5 additions & 6 deletions src/pages/404.astro
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
---
import type { SiteLocale } from '@lib/i18n/types';
import type {
NotFoundPageQuery,
NotFoundPageRecord,
} from '@lib/datocms/types';
import type { NotFoundPageQuery, NotFoundPageRecord } from '@lib/datocms/types';
import { datocmsRequest } from '@lib/datocms';
import { noIndexTag, titleTag } from '@lib/seo';
import Layout from '@layouts/Default.astro';
Expand All @@ -15,14 +12,16 @@ export const prerender = false;

Astro.response.status = 404;

const { locale } = Astro.params as { locale: SiteLocale };
const localeFromPath = Astro.params.locale as SiteLocale;
const localeFromQuery = Astro.url.searchParams.get('locale') as SiteLocale;
const locale = localeFromQuery || localeFromPath;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now needed to have the Astro.rewrite('/404/') work in [locale]/[...path]/index.astro. It's the tradeoff for no longer needing to cast the response from the page query. Worth it? Or rather have that one fail as it did before?

const { page } = (await datocmsRequest<NotFoundPageQuery>({
query,
variables: { locale },
})) as { page: NotFoundPageRecord };
---

<Layout pageUrls={[]} seoMetaTags={[noIndexTag, titleTag(page.title)]}>
<h1>{page.title} {Astro.params.locale}</h1>
<h1>{page.title}</h1>
<Blocks blocks={page.bodyBlocks as AnyBlock[]} />
</Layout>
31 changes: 21 additions & 10 deletions src/pages/[locale]/[...path]/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,30 @@ type Params = {

const { locale, path } = Astro.params as Params;
const variables = { locale, slug: getPageSlugFromPath(path) };
const { page } = (await datocmsRequest<PageQuery>({ query, variables })) as {
page: NonNullable<PageQuery['page']>; // Only NonNullable when statically generated. Handle as a 404 when this is a server route!
};
const breadcrumbs = [...getParentPages(page), page].map((page) =>
const { page } = await datocmsRequest<PageQuery>({ query, variables });

if (!page) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check ensures this type casting is no longer needed:

as {
  page: NonNullable<PageQuery['page']>; // Only NonNullable when statically generated. Handle as a 404 when this is a server route!
};

But it makes the 404 rewrite and locale setting in that file more complex. Worth it?

return Astro.rewrite(`/404/?locale=${locale}`);
}

const canonicalUrl = getPageHref({ locale, record: page });
if (Astro.url.pathname !== canonicalUrl) {
return Astro.redirect(canonicalUrl);
}

const breadcrumbs = [...getParentPages(page), page].map((record) =>
formatBreadcrumb({
text: page.title,
href: getPageHref({ locale, record: page }),
text: record.title,
href: getPageHref({ locale, record }),
})
);
const pageUrls = (page._allSlugLocales || []).map(({ locale }) => ({
locale: locale as SiteLocale,
pathname: getPageHref({ locale: locale as SiteLocale, record: page }),
})) as PageUrl[];

const pageLocales = (page._allSlugLocales?.map(({ locale }) => locale) ??
[]) as SiteLocale[];
const pageUrls = pageLocales.map((locale) => ({
locale,
pathname: getPageHref({ locale, record: page }),
})) satisfies PageUrl[];
---

<Layout
Expand Down
7 changes: 7 additions & 0 deletions src/pages/api/reroute/_page.query.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import '@lib/routing/PageRoute.fragment.graphql'

query ReroutePage($locale: SiteLocale!, $slug: String!) {
page(locale: $locale, filter: { slug: { eq: $slug } }) {
...PageRoute
}
}
38 changes: 38 additions & 0 deletions src/pages/api/reroute/page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { APIRoute } from 'astro';
import { datocmsRequest } from '@lib/datocms';
import type { ReroutePageQuery, SiteLocale } from '@lib/datocms/types';
import { getPageHref } from '@lib/routing';
import query from './_page.query.graphql';

export const prerender = false;

const jsonResponse = (data: object, status: number = 200) => {
return new Response(JSON.stringify(data), {
status,
headers: {
'Content-Type': 'application/json',
},
});
};

export const GET: APIRoute = async ({ request }) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a generic alternate approach that I used in the nododos-website. But I think the canonical url check in [locale]/[...path]/index.astro is easier and probably the way to go.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm. I guess the canonical check works on localhost and preview as the generic page route is then always handled dynamically on the server. However the production environment has prerendered pages and will throw a 404 I suppose. So I guess we do need this reroute API handler.

In that case the config of the Preview field of the generic page will need to use that one instead.

const locale = new URL(request.url).searchParams.get('locale') as SiteLocale;
if (!locale) {
return jsonResponse({ error: 'Missing \'locale\' parameter' }, 400);
}

const slug = new URL(request.url).searchParams.get('slug');
if (!slug) {
return jsonResponse({ error: 'Missing \'slug\' parameter' }, 400);
}

const { page } = (await datocmsRequest<ReroutePageQuery>({ query, variables: { slug, locale } }));
if (!page) {
return jsonResponse({ error: 'Page not found' }, 404);
}

return new Response('',{
status: 307,
headers: { 'Location': getPageHref({ locale, record: page }) },
});
};
Loading